├── tests ├── test_ticket.py ├── _files │ ├── boo │ │ └── test.txt │ ├── snap_unicode │ │ └── хуйюй мухуюй.txt │ ├── mcloud.yml │ └── ct_bash │ │ └── Dockerfile ├── test_mfcloud_cli.py ├── test_txhttp.py ├── test_deffered.py ├── test_inject_context.py ├── test_redis_api.py ├── test_txdocker.py ├── test_txdocker_api.py ├── test_events.py ├── test_rpc_client.py ├── test_image_builders.py ├── test_application.py └── test_tasks.py ├── version.txt ├── docs ├── source │ ├── _static │ │ ├── .gitkeep │ │ ├── mfcloud.png │ │ ├── do_deploy │ │ │ ├── go.png │ │ │ ├── wait.png │ │ │ ├── ready_ip.png │ │ │ ├── ssh_keys.png │ │ │ ├── create_button.png │ │ │ ├── region_and_os.png │ │ │ └── hostname_and_size.png │ │ ├── mfcloud_simpleapp.png │ │ └── do_deploy_aws │ │ │ ├── 12_ip.png │ │ │ ├── 6_launch.png │ │ │ ├── 9_launch.png │ │ │ ├── 10_ssh_key.png │ │ │ ├── 3_key_pairs.png │ │ │ ├── 5_instances.png │ │ │ ├── 7_select_os.png │ │ │ ├── 11_launching.png │ │ │ ├── 1_select_ec2.png │ │ │ ├── 4_import_key.png │ │ │ ├── 2_select_region.png │ │ │ └── 8_instance_type.png │ ├── manage │ │ ├── ssl.rst │ │ ├── backup.rst │ │ ├── update.rst │ │ └── uninstall.rst │ ├── plugins │ ├── api_ws.rst │ ├── reference │ │ ├── plugin_api.rst │ │ ├── api.rst │ │ └── cli.rst │ ├── plantuml.jar │ ├── reference.rst │ ├── use.rst │ ├── plugins.rst │ ├── guide.rst │ ├── api.rst │ ├── README │ ├── manage.rst │ ├── overview │ │ └── whatismcloud.rst │ ├── use │ │ ├── env.rst │ │ ├── sync.rst │ │ ├── debug.rst │ │ ├── machine.rst │ │ ├── volumes.rst │ │ ├── remote.rst │ │ ├── shell.rst │ │ ├── publish.rst │ │ ├── zero_downtime.rst │ │ └── app.rst │ ├── index.rst │ ├── api_yml.rst │ ├── dev.rst │ ├── os_macos.rst │ └── guide │ │ ├── 4_production.rst │ │ └── 2_hello_app.rst ├── build.config.js ├── package.json └── gulpfile.js ├── mcloud ├── version.py ├── static │ ├── client │ │ ├── src │ │ │ ├── app │ │ │ │ ├── home │ │ │ │ │ ├── home.tpl.html │ │ │ │ │ └── home.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── getting-started.js │ │ │ │ │ └── getting-started.tpl.html │ │ │ │ └── app.js │ │ │ ├── scss │ │ │ │ └── main.scss │ │ │ └── common │ │ │ │ ├── footer.js │ │ │ │ ├── header.js │ │ │ │ ├── filters │ │ │ │ └── uppercase.js │ │ │ │ ├── services │ │ │ │ └── dataService.js │ │ │ │ ├── directives │ │ │ │ └── version.js │ │ │ │ ├── header.tpl.html │ │ │ │ ├── footer.tpl.html │ │ │ │ └── interceptors │ │ │ │ └── httpInterceptor.js │ │ ├── test │ │ │ ├── unit-results │ │ │ │ └── README.md │ │ │ ├── screenshots │ │ │ │ └── README.md │ │ │ ├── utils.js │ │ │ ├── unit │ │ │ │ ├── common │ │ │ │ │ ├── filters │ │ │ │ │ │ └── uppercase.spec.js │ │ │ │ │ └── directives │ │ │ │ │ │ └── version.spec.js │ │ │ │ └── app │ │ │ │ │ ├── app.spec.js │ │ │ │ │ └── home │ │ │ │ │ └── home.spec.js │ │ │ └── e2e │ │ │ │ └── 01.home.scenario.js │ │ ├── favicon.ico │ │ ├── assets │ │ │ └── images │ │ │ │ ├── bower-logo.png │ │ │ │ ├── gulp-logo.png │ │ │ │ └── angular-logo.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── humans.txt │ │ └── index.html │ ├── .bowerrc │ ├── bower.json │ ├── .gitignore │ └── package.json ├── static_old │ ├── .bowerrc │ ├── components │ │ ├── term.js │ │ │ ├── index.js │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── Makefile │ │ │ ├── test │ │ │ │ ├── data.diff │ │ │ │ ├── index.js │ │ │ │ ├── bench.js │ │ │ │ └── index.html │ │ │ ├── .bower.json │ │ │ ├── package.json │ │ │ ├── LICENSE │ │ │ ├── lib │ │ │ │ └── index.js │ │ │ ├── example │ │ │ │ ├── index.html │ │ │ │ └── index.js │ │ │ └── README.md │ │ ├── angular │ │ │ ├── angular.min.js.gzip │ │ │ ├── bower.json │ │ │ ├── .bower.json │ │ │ ├── angular-csp.css │ │ │ ├── package.json │ │ │ └── README.md │ │ ├── xterm.js │ │ │ ├── bower.json │ │ │ ├── addons │ │ │ │ ├── fullscreen │ │ │ │ │ ├── fullscreen.css │ │ │ │ │ └── fullscreen.js │ │ │ │ └── attach │ │ │ │ │ ├── attach.js │ │ │ │ │ └── index.html │ │ │ ├── README.md │ │ │ ├── .bower.json │ │ │ └── LICENSE │ │ └── reconnectingWebsocket │ │ │ ├── bower.json │ │ │ ├── package.json │ │ │ ├── .bower.json │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ └── reconnecting-websockets.d.ts │ ├── assets │ │ ├── images │ │ │ ├── biglogo.png │ │ │ ├── circlebg.png │ │ │ ├── plaidbg.png │ │ │ ├── stripebg.png │ │ │ ├── mcloudicon.png │ │ │ ├── callout.svg │ │ │ ├── mcloudlogo.svg │ │ │ └── mcloudlogomono.svg │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── css │ │ │ └── app.css │ │ └── js │ │ │ └── app.js │ ├── docs │ │ └── index.html │ └── bower.json ├── plugins │ ├── __init__.py │ ├── monitor.py │ ├── hosts.py │ └── dns.py ├── __init__.py ├── metadata.py ├── plugin.py ├── test_utils.py ├── interrupt.py ├── ssl.py ├── container.py ├── util.py ├── main.py ├── shell.py └── events.py ├── pytest.ini ├── plugins ├── tpl │ ├── cookiecutter.json │ └── {{cookiecutter.name}} │ │ ├── README.rst │ │ ├── mcloud_{{cookiecutter.name}}.py │ │ └── setup.py ├── browser-auto-open │ ├── README.rst │ ├── mcloud_browser_auto_open.py │ └── setup.py ├── simple_publish │ ├── README.rst │ ├── mcloud_simple_publish.py │ └── setup.py └── haproxy │ ├── setup.py │ └── README.rst ├── .coveragerc ├── test.sh ├── .bumpversion.cfg ├── requirements-dev.txt ├── .gitmodules ├── MANIFEST.in ├── README.rst ├── tox.ini ├── .gitignore ├── test.py ├── fabfile.py ├── release.py ├── CHANGES.txt └── setup.py /tests/test_ticket.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 0.6.1 2 | -------------------------------------------------------------------------------- /docs/source/_static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/_files/boo/test.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/manage/ssl.rst: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/source/plugins: -------------------------------------------------------------------------------- 1 | ../../plugins -------------------------------------------------------------------------------- /tests/test_mfcloud_cli.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mcloud/version.py: -------------------------------------------------------------------------------- 1 | version = '1.0.3' 2 | -------------------------------------------------------------------------------- /tests/_files/snap_unicode/хуйюй мухуюй.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mcloud/static/client/src/app/home/home.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/api_ws.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Websocket api 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/_files/mcloud.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | controller: 4 | build: foo -------------------------------------------------------------------------------- /mcloud/static_old/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "components" 3 | } -------------------------------------------------------------------------------- /docs/source/reference/plugin_api.rst: -------------------------------------------------------------------------------- 1 | 2 | Plugin API 3 | --------------- 4 | -------------------------------------------------------------------------------- /mcloud/static/client/src/scss/main.scss: -------------------------------------------------------------------------------- 1 | 2 | .boo { 3 | color: white; 4 | } -------------------------------------------------------------------------------- /docs/source/reference/api.rst: -------------------------------------------------------------------------------- 1 | 2 | Mcloud Remote API 3 | ------------------- 4 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_paths=. 3 | norecursedirs = .env .git 4 | twisted = 1 -------------------------------------------------------------------------------- /plugins/tpl/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "booo", 3 | "version": "0.1.0" 4 | } -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/index.js'); 2 | -------------------------------------------------------------------------------- /docs/source/plantuml.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/plantuml.jar -------------------------------------------------------------------------------- /mcloud/static/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/src/vendor", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /mcloud/static/client/test/unit-results/README.md: -------------------------------------------------------------------------------- 1 | ###A coverage report will be generated by karma. 2 | -------------------------------------------------------------------------------- /docs/source/_static/mfcloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/mfcloud.png -------------------------------------------------------------------------------- /docs/source/manage/backup.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Backup mcloud 4 | ============================================ 5 | 6 | -------------------------------------------------------------------------------- /mcloud/static/client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static/client/favicon.ico -------------------------------------------------------------------------------- /mcloud/static/client/test/screenshots/README.md: -------------------------------------------------------------------------------- 1 | ###Screenshots of failing tests will be placed in this folder. 2 | -------------------------------------------------------------------------------- /docs/source/_static/do_deploy/go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy/go.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy/wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy/wait.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy/ready_ip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy/ready_ip.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy/ssh_keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy/ssh_keys.png -------------------------------------------------------------------------------- /docs/source/_static/mfcloud_simpleapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/mfcloud_simpleapp.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/12_ip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/12_ip.png -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/biglogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/images/biglogo.png -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/circlebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/images/circlebg.png -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/plaidbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/images/plaidbg.png -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/stripebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/images/stripebg.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy/create_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy/create_button.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy/region_and_os.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy/region_and_os.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/6_launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/6_launch.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/9_launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/9_launch.png -------------------------------------------------------------------------------- /mcloud/static_old/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/mcloudicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/images/mcloudicon.png -------------------------------------------------------------------------------- /plugins/browser-auto-open/README.rst: -------------------------------------------------------------------------------- 1 | Browser auto-open 2 | ============================================ 3 | 4 | not working yet. Don't use. -------------------------------------------------------------------------------- /tests/_files/ct_bash/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM ubuntu 3 | 4 | RUN apt-get update 5 | 6 | CMD while true; do echo hello world; sleep 1; done 7 | -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/10_ssh_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/10_ssh_key.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/3_key_pairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/3_key_pairs.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/5_instances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/5_instances.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/7_select_os.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/7_select_os.png -------------------------------------------------------------------------------- /docs/source/reference.rst: -------------------------------------------------------------------------------- 1 | 2 | References & API 3 | ------------------- 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :glob: 8 | 9 | reference/* -------------------------------------------------------------------------------- /mcloud/static/client/assets/images/bower-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static/client/assets/images/bower-logo.png -------------------------------------------------------------------------------- /mcloud/static/client/assets/images/gulp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static/client/assets/images/gulp-logo.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy/hostname_and_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy/hostname_and_size.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/11_launching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/11_launching.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/1_select_ec2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/1_select_ec2.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/4_import_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/4_import_key.png -------------------------------------------------------------------------------- /docs/source/use.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Using mcloud 4 | ---------------------- 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | use/* -------------------------------------------------------------------------------- /mcloud/static/client/assets/images/angular-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static/client/assets/images/angular-logo.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/2_select_region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/2_select_region.png -------------------------------------------------------------------------------- /docs/source/_static/do_deploy_aws/8_instance_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/docs/source/_static/do_deploy_aws/8_instance_type.png -------------------------------------------------------------------------------- /mcloud/static/client/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static/client/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /mcloud/static_old/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /mcloud/static_old/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/source/plugins.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Plugins 4 | ---------------------- 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | plugins/*/README -------------------------------------------------------------------------------- /mcloud/static_old/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /mcloud/static_old/components/angular/angular.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/components/angular/angular.min.js.gzip -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | # Don't complain if non-runnable code isn't run: 4 | if 0: 5 | if __name__ == .__main__.: 6 | 7 | pass 8 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xterm.js", 3 | "version": "0.9.2", 4 | "ignore": ["demo", "docs", "test", ".gitignore"] 5 | } 6 | -------------------------------------------------------------------------------- /plugins/tpl/{{cookiecutter.name}}/README.rst: -------------------------------------------------------------------------------- 1 | ModeraCloud {{cookiecutter.name}} plugin 2 | ============================================ 3 | 4 | not working yet. Don't use. -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | MCLOUD="python -c \"import sys,socket,os;s=socket.socket(1,1);s.connect('/var/run/mcloud');s.send('[%s] %s'%(os.uname()[1],sys.stdin.read().strip()));s.close()\"" -------------------------------------------------------------------------------- /mcloud/static_old/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /mcloud/static_old/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /mcloud/static_old/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modera/mcloud/HEAD/mcloud/static_old/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/source/guide.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Getting started 4 | ------------------------------- 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :glob: 9 | 10 | guide/* 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | 2 | ========================= 3 | API Reference 4 | ========================= 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | api_yml 10 | 11 | 12 | -------------------------------------------------------------------------------- /mcloud/static_old/components/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.27", 4 | "main": "./angular.js", 5 | "ignore": [], 6 | "dependencies": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/source/README: -------------------------------------------------------------------------------- 1 | Run `sphinx-apidoc -o . ../../mcloud' in this directory. 2 | 3 | This will generate `modules.rst' and `mcloud.rst'. 4 | 5 | Then include `modules.rst' in your `index.rst' file. -------------------------------------------------------------------------------- /mcloud/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | class Plugin(object): 3 | pass 4 | 5 | 6 | class PluginFatalError(Exception): 7 | pass 8 | 9 | 10 | class PluginInitError(PluginFatalError): 11 | pass -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.swp 3 | build/* 4 | .lock-wscript 5 | out/ 6 | Makefile.gyp 7 | *.Makefile 8 | *.target.gyp.mk 9 | *.node 10 | example/*.log 11 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.0.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:mcloud/version.py] 7 | 8 | [bumpversion:file:plugins/haproxy/setup.py] 9 | 10 | -------------------------------------------------------------------------------- /mcloud/static_old/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Sorry, no docs built. 9 | 10 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | build/ 3 | .lock-wscript 4 | out/ 5 | Makefile.gyp 6 | *.Makefile 7 | *.target.gyp.mk 8 | node_modules/ 9 | img/ 10 | test/ 11 | *.node 12 | example/*.log 13 | -------------------------------------------------------------------------------- /docs/source/manage.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ======================================== 4 | Manage mcloud installation 5 | ======================================== 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | :glob: 10 | 11 | manage/* 12 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @cp src/term.js term.js 3 | @uglifyjs -o term.min.js term.js 4 | 5 | clean: 6 | @rm term.js 7 | @rm term.min.js 8 | 9 | bench: 10 | @node test/bench 11 | 12 | .PHONY: clean all 13 | -------------------------------------------------------------------------------- /plugins/simple_publish/README.rst: -------------------------------------------------------------------------------- 1 | ModeraCloud Simple publish plugin 2 | ============================ 3 | 4 | Plugin exposes application web port as external 80 port. 5 | 6 | Installation: 7 | 8 | pip install mcloud-plugin-simple-publish 9 | 10 | -------------------------------------------------------------------------------- /tests/test_txhttp.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from mcloud import txhttp 4 | import pytest 5 | 6 | 7 | @pytest.inlineCallbacks 8 | def test_basic_connect(): 9 | 10 | r = yield txhttp.get('http://google.com') 11 | 12 | assert r.code == 200 13 | 14 | 15 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/addons/fullscreen/fullscreen.css: -------------------------------------------------------------------------------- 1 | .xterm.fullscreen { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | width: auto; 8 | height: auto; 9 | z-index: 255; 10 | } -------------------------------------------------------------------------------- /mcloud/static/client/src/common/footer.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function footerCtrl($log) { 5 | $log.debug('Footer loaded'); 6 | } 7 | 8 | angular.module('common.footer', []) 9 | .controller('FooterCtrl', footerCtrl); 10 | })(); 11 | -------------------------------------------------------------------------------- /mcloud/static/client/src/common/header.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function headerCtrl($log) { 5 | $log.debug('Header loaded'); 6 | } 7 | 8 | angular.module('common.header', []) 9 | .controller('HeaderCtrl', headerCtrl); 10 | })(); 11 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest-pythonpath 2 | pytest 3 | pytest-cov 4 | mock 5 | pep8 6 | pytest-pep8 7 | sphinx 8 | sphinx_rtd_theme 9 | pytest-twisted 10 | flexmock 11 | sphinxcontrib-plantuml 12 | sphinx-argparse 13 | GitPython==0.3.2.RC1 14 | fabric 15 | cookiecutter -------------------------------------------------------------------------------- /docs/source/manage/update.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Updating ModeraCloud 4 | ---------------------------------- 5 | 6 | To update:: 7 | 8 | $ docker exec -it mcloud mcloud-plugins install -U mcloud 9 | 10 | And restart service:: 11 | 12 | $ docker restart mcloud 13 | 14 | -------------------------------------------------------------------------------- /mcloud/static_old/assets/css/app.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .app-list .nav-tabs > li { 4 | margin-bottom: -2px; 5 | } 6 | 7 | .app-list .refresh-link { 8 | cursor: pointer; 9 | } 10 | 11 | .terminal { 12 | font-family: Monospace, Monaco, Consolas; 13 | font-size: 12px; 14 | } -------------------------------------------------------------------------------- /mcloud/__init__.py: -------------------------------------------------------------------------------- 1 | """Production cloud deployments of fig infrastructure with docker""" 2 | 3 | from mcloud import metadata 4 | 5 | 6 | __version__ = metadata.version 7 | __author__ = metadata.authors[0] 8 | __license__ = metadata.license 9 | __copyright__ = metadata.copyright 10 | -------------------------------------------------------------------------------- /mcloud/static/client/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | Normalize.css, AngularJS 16 | -------------------------------------------------------------------------------- /mcloud/static/client/test/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | 4 | exports.takeScreenshot = function(browser, filename) { 5 | browser.takeScreenshot().then(function(png) { 6 | fs.writeFileSync('./client/test/screenshots/' + filename + '.png', png, 'base64'); 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/source/_themes"] 2 | path = docs/source/_themes 3 | url = https://github.com/mitsuhiko/flask-sphinx-themes.git 4 | [submodule "docs/source/themes/cloud.modera.org"] 5 | path = docs/source/themes/cloud.modera.org 6 | url = ssh://git@stash.dev.modera.org:7999/web/cloud.modera.org.git 7 | -------------------------------------------------------------------------------- /tests/test_deffered.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from twisted.internet import defer, reactor 3 | 4 | 5 | def boo(): 6 | d = defer.Deferred() 7 | reactor.callLater(0.1, d.callback, 10) 8 | return d 9 | 10 | @pytest.inlineCallbacks 11 | def test_boo_defferred(): 12 | a = yield boo() 13 | assert 10 == a -------------------------------------------------------------------------------- /mcloud/static/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-kickstart", 3 | "dependencies": { 4 | "normalize-css": "~3.0.3", 5 | "angular": "~1.3.0", 6 | "angular-ui-router": "~0.2.14", 7 | "angular-semantic-ui": "~0.0.3" 8 | }, 9 | "devDependencies": { 10 | "angular-mocks": "~1.3.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mcloud/static/client/src/common/filters/uppercase.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function uppercase() { 5 | return function(text) { 6 | return text ? text.toUpperCase() : text; 7 | }; 8 | } 9 | 10 | angular.module('common.filters.uppercase', []) 11 | .filter('uppercase', uppercase); 12 | })(); 13 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/test/data.diff: -------------------------------------------------------------------------------- 1 | 167a168,170 2 | > var stream = fs.createWriteStream(__dirname + '/../test/data.js'); 3 | > stream.write('this.data = [\n'); 4 | > 5 | 169a173 6 | > stream.write(' ' + JSON.stringify(data) + ',\n'); 7 | 182a187,189 8 | > 9 | > stream.write('];\n'); 10 | > stream.end(); 11 | -------------------------------------------------------------------------------- /mcloud/static/client/src/common/services/dataService.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function dataService() { 5 | return { 6 | get: function() { 7 | return ['some', 'data']; 8 | } 9 | }; 10 | } 11 | 12 | angular.module('common.services.data', []) 13 | .factory('DataService', dataService); 14 | })(); 15 | -------------------------------------------------------------------------------- /docs/source/overview/whatismcloud.rst: -------------------------------------------------------------------------------- 1 | 2 | What is mcloud 3 | =========================== 4 | 5 | 6 | 7 | 8 | 9 | .. uml:: 10 | 11 | [Mcloud client] as cli 12 | [Mcloud server] as srv 13 | 14 | cli .right.> srv : WebsocketAPI 15 | 16 | 17 | srv .down.> [Docker1] : RemoteAPI 18 | srv .down.> [Docker2] : RemoteAPI 19 | srv .down.> [Docker3] : RemoteAPI 20 | 21 | -------------------------------------------------------------------------------- /mcloud/static/.gitignore: -------------------------------------------------------------------------------- 1 | # general 2 | .idea 3 | .DS_Store 4 | *~ 5 | .sass-cache 6 | 7 | # package control 8 | node_modules 9 | client/src/vendor 10 | 11 | # logs 12 | npm-debug.log 13 | 14 | # build 15 | build/tmp 16 | build/dist 17 | 18 | #test 19 | client/test/screenshots/* 20 | !client/test/screenshots/README.md 21 | client/test/unit-results/* 22 | !client/test/unit-results/README.md 23 | -------------------------------------------------------------------------------- /docs/source/reference/cli.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _cli: 3 | 4 | 5 | ========================================== 6 | Command-line reference 7 | ========================================== 8 | 9 | ModeraCloudCloud command-line basic syntax is:: 10 | 11 | $ mcloud command [... arguments ...] 12 | 13 | You can also start mcloud in shell mode:: 14 | 15 | $ mcloud 16 | 17 | Then you can omit *mcloud* prefix. 18 | -------------------------------------------------------------------------------- /docs/source/use/env.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Environment variables 5 | ===================== 6 | 7 | You can assign extra environment variables that will be passed to containers:: 8 | 9 | $ mcloud set VAR_NAME val 10 | $ mcloud unset VAR_NAME 11 | $ mcloud vars 12 | 13 | Variables are assigned on container *creation*, so you need to rebuild container if you need changes to be applied on running container. 14 | 15 | -------------------------------------------------------------------------------- /tests/test_inject_context.py: -------------------------------------------------------------------------------- 1 | 2 | import inject 3 | from mcloud.util import inject_services 4 | 5 | 6 | def test_inject_services(): 7 | class Boo(): 8 | pass 9 | 10 | class Baz(): 11 | pass 12 | 13 | def configure(binder): 14 | binder.bind(Boo, Baz()) 15 | 16 | with inject_services(configure): 17 | instance = inject.instance(Boo) 18 | assert isinstance(instance, Baz) 19 | -------------------------------------------------------------------------------- /mcloud/static_old/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcloud-web", 3 | "version": "0.8.23", 4 | "homepage": "https://github.com/modera/mcloud", 5 | "authors": [ 6 | "Alex Rudakov " 7 | ], 8 | "description": "ModeraCloud web ui", 9 | "license": "MIT", 10 | "private": true, 11 | "dependencies": { 12 | "angular": "~1.2.26", 13 | "reconnectingWebsocket": "*", 14 | "term.js": "~0.0.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mcloud/static/client/src/common/directives/version.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function versionDirective(version) { 5 | return { 6 | restrict: 'A', 7 | /*jshint unused:false*/ 8 | link: function(scope, elm, attrs) { 9 | elm.text(version); 10 | } 11 | }; 12 | } 13 | 14 | angular.module('common.directives.version', []) 15 | .directive('appVersion', versionDirective); 16 | })(); 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Informational files 2 | include README.rst 3 | include LICENSE 4 | 5 | prune .env 6 | prune docs 7 | prune tests 8 | 9 | 10 | 11 | # Exclude any compile Python files (most likely grafted by tests/ directory). 12 | global-exclude *.pyc 13 | 14 | # Setup-related things 15 | # include pavement.py 16 | # include requirements-dev.txt 17 | # include requirements.txt 18 | include setup.py 19 | # include tox.ini 20 | 21 | recursive-include mcloud/static * 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ModeraCloud 2 | ============ 3 | 4 | Deployment and Cluster orchestration tools made by developers for developers. 5 | 6 | .. image:: https://badges.gitter.im/Join%20Chat.svg 7 | :alt: Join the chat at https://gitter.im/modera/mcloud 8 | :target: https://gitter.im/modera/mcloud?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 9 | 10 | 11 | Website & Documentation 12 | ------------------------- 13 | 14 | http://mcloud.io/ 15 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "term.js", 3 | "homepage": "https://github.com/vahe/term.js", 4 | "version": "0.0.3", 5 | "_release": "0.0.3", 6 | "_resolution": { 7 | "type": "version", 8 | "tag": "v0.0.3", 9 | "commit": "04198dc09acc0276983619d68a2ef697c6a509be" 10 | }, 11 | "_source": "git://github.com/vahe/term.js.git", 12 | "_target": "~0.0.3", 13 | "_originalSource": "term.js", 14 | "_direct": true 15 | } -------------------------------------------------------------------------------- /mcloud/static/client/src/common/header.tpl.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /mcloud/static/client/test/unit/common/filters/uppercase.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint undef:false*/ 2 | (function() { 3 | 'use strict'; 4 | 5 | describe('Filters: uppercase', function() { 6 | 7 | var filter; 8 | beforeEach(module('common.filters.uppercase')); 9 | beforeEach(inject(function($filter) { 10 | filter = $filter; 11 | })); 12 | 13 | it('should create an uppercase string', function() { 14 | expect(filter('uppercase')('hello')).toEqual('HELLO'); 15 | }); 16 | }); 17 | })(); 18 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/README.md: -------------------------------------------------------------------------------- 1 | # xterm.js 2 | 3 | xterm, in the browser. 4 | 5 | ### Contribution and License Agreement 6 | 7 | If you contribute code to this project, you are implicitly allowing your code 8 | to be distributed under the MIT license. You are also implicitly verifying that 9 | all code is your original work. 10 | 11 | ## License 12 | 13 | Copyright (c) 2014, sourceLair Limited (www.sourcelair.com) (MIT License) 14 | Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 15 | -------------------------------------------------------------------------------- /mcloud/static_old/components/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.27", 4 | "main": "./angular.js", 5 | "ignore": [], 6 | "dependencies": {}, 7 | "homepage": "https://github.com/angular/bower-angular", 8 | "_release": "1.2.27", 9 | "_resolution": { 10 | "type": "version", 11 | "tag": "v1.2.27", 12 | "commit": "4429039fc57ddb810735179be1549c3bbb8e09a8" 13 | }, 14 | "_source": "git://github.com/angular/bower-angular.git", 15 | "_target": "~1.2.26", 16 | "_originalSource": "angular" 17 | } -------------------------------------------------------------------------------- /plugins/tpl/{{cookiecutter.name}}/mcloud_{{cookiecutter.name}}.py: -------------------------------------------------------------------------------- 1 | import inject 2 | from mcloud.events import EventBus 3 | from mcloud.plugin import IMcloudPlugin 4 | from mcloud.plugins import Plugin 5 | from twisted.internet.defer import inlineCallbacks 6 | from zope.interface import implements 7 | 8 | class {{cookiecutter.name|capitalize}}Plugin(Plugin): 9 | implements(IMcloudPlugin) 10 | 11 | eb = inject.attr(EventBus) 12 | settings = inject.attr('settings') 13 | 14 | @inlineCallbacks 15 | def setup(self): 16 | yield None 17 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | ============== 3 | Welcome 4 | ============== 5 | 6 | Welcome to *ModeraCloud* - a tool that helps you manage Docker based deployments. 7 | 8 | - Describe deployment in a simple configuration file. 9 | - Keep the configuration in a VCS with your application code. 10 | - Use command-line tool to manage the containers locally and remotely. 11 | 12 | 13 | Contents 14 | ------------------ 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | 19 | guide 20 | 21 | use 22 | 23 | manage 24 | reference 25 | 26 | plugins 27 | 28 | dev 29 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xterm.js", 3 | "version": "0.9.3", 4 | "ignore": [ 5 | "demo", 6 | "docs", 7 | "test", 8 | ".gitignore" 9 | ], 10 | "homepage": "https://github.com/sourcelair/xterm.js", 11 | "_release": "0.9.3", 12 | "_resolution": { 13 | "type": "version", 14 | "tag": "0.9.3", 15 | "commit": "fb1c4842243e9e5e7aa330754e016dd21e55f81a" 16 | }, 17 | "_source": "git://github.com/sourcelair/xterm.js.git", 18 | "_target": "~0.9.3", 19 | "_originalSource": "xterm.js", 20 | "_direct": true 21 | } -------------------------------------------------------------------------------- /tests/test_redis_api.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | import txredisapi as redis 5 | 6 | @pytest.inlineCallbacks 7 | def test_incr(): 8 | rc = yield redis.Connection(dbid=2) 9 | yield rc.flushdb() 10 | 11 | yield rc.set("foo", 4) 12 | 13 | v = yield rc.get("foo") 14 | 15 | assert v == 4 16 | 17 | v = yield rc.incr("foo") 18 | 19 | assert v == 5 20 | 21 | yield rc.disconnect() 22 | 23 | @pytest.inlineCallbacks 24 | def test_hash_set(): 25 | 26 | rc = yield redis.Connection(dbid=2) 27 | yield rc.flushdb() 28 | 29 | yield rc.hset("foo", "bar", "baz") 30 | 31 | v = yield rc.hget("foo", "bar") 32 | 33 | assert v == 'baz' -------------------------------------------------------------------------------- /docs/source/manage/uninstall.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Uninstalling mcloud 4 | ============================================ 5 | 6 | 7 | 8 | Mcloud install from packages 9 | ---------------------------------- 10 | 11 | Remove package:: 12 | 13 | $ apt-get remove mcloud-full 14 | 15 | 16 | Manualy installed mcloud update 17 | ---------------------------------- 18 | 19 | Remove package:: 20 | 21 | $ apt-get remove mcloud 22 | 23 | 24 | Source installed mcloud update 25 | ---------------------------------- 26 | 27 | - Remove upstart/supervisor script 28 | - Remove mcloud commands: sudo rm /usr/local/bin/mcloud* 29 | - Remove mcloud home: sudo rm -rf /opt/mcloud 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /mcloud/static_old/components/angular/angular-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | .ng-animate-block-transitions { 16 | transition:0s all!important; 17 | -webkit-transition:0s all!important; 18 | } 19 | 20 | /* show the element during a show/hide animation when the 21 | * animation is ongoing, but the .ng-hide class is active */ 22 | .ng-hide-add-active, .ng-hide-remove { 23 | display: block!important; 24 | } 25 | -------------------------------------------------------------------------------- /mcloud/static_old/components/reconnectingWebsocket/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reconnecting-websocket", 3 | "main": "reconnecting-websocket.js", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/joewalnes/reconnecting-websocket", 6 | "authors": [ 7 | "Joe Walnes", 8 | "headlessme " 9 | ], 10 | "description": "A small decorator for the JavaScript WebSocket API that automatically reconnects", 11 | "moduleType": [ 12 | "amd", 13 | "globals", 14 | "node" 15 | ], 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /docs/build.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //basic configuration object used by gulp tasks 4 | module.exports = { 5 | port: 3000, 6 | tmp: 'var/build/tmp', 7 | dist: 'var/build/dist', 8 | base: 'client', 9 | source: [ 10 | 'source/**/*.rst' 11 | ], 12 | site_source: [ 13 | 'source/themes/cloud.modera.org/jekyll/**/*' 14 | ], 15 | site_doc_theme: [ 16 | 'source/themes/cloud.modera.org/docs_theme/**/*' 17 | ], 18 | banner: ['/**', 19 | ' * <%= pkg.name %> - <%= pkg.description %>', 20 | ' * @version v<%= pkg.version %>', 21 | ' * @link <%= pkg.homepage %>', 22 | ' * @license <%= pkg.license %>', 23 | ' */', 24 | '' 25 | ].join('\n') 26 | }; 27 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "term.js", 3 | "description": "A terminal written in javascript", 4 | "author": "Christopher Jeffrey", 5 | "version": "0.0.3", 6 | "main": "./index.js", 7 | "preferGlobal": false, 8 | "repository": "git://github.com/chjj/term.js.git", 9 | "homepage": "https://github.com/chjj/term.js", 10 | "bugs": { "url": "https://github.com/chjj/term.js/issues" }, 11 | "keywords": ["tty", "terminal", "term", "xterm"], 12 | "tags": ["tty", "terminal", "term", "xterm"], 13 | "engines": { "node": ">= 0.8.0" }, 14 | "devDependencies": { 15 | "express": "3.1.0", 16 | "socket.io": "0.9.13", 17 | "pty.js": "0.2.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/source/use/sync.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ====================== 4 | Synchronizing files 5 | ====================== 6 | 7 | mcloud sync command allows to copy files between local and remote deployments. 8 | 9 | 10 | local dir to remote volume 11 | ------------------------------ 12 | 13 | mcloud sync my_local_dir yyy.xxx@my_mcloud_server.com:/var/www 14 | 15 | 16 | remote volume to local dir 17 | ------------------------------- 18 | 19 | mcloud sync yyy.xxx@my_mcloud_server.com:/var/www my_local_dir 20 | 21 | upload to remote application root volume (where mcloud.yml resides) 22 | ---------------------------------------------------------------------- 23 | 24 | mcloud sync my_local_dir xxx@my_mcloud_server.com 25 | 26 | 27 | -------------------------------------------------------------------------------- /mcloud/metadata.py: -------------------------------------------------------------------------------- 1 | """Project metadata 2 | 3 | Information describing the project. 4 | """ 5 | import os 6 | from mcloud.version import version as mcloud_version 7 | 8 | root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 9 | 10 | # The package name, which is also the "UNIX name" for the project. 11 | package = 'mcloud' 12 | project = "mcloud" 13 | project_no_spaces = project.replace(' ', '') 14 | version = mcloud_version 15 | description = 'A tool that helps you manage Docker based deployments' 16 | authors = ['Alex Rudakov'] 17 | authors_string = ', '.join(authors) 18 | emails = ['ribozz@gmail.com'] 19 | license = 'Apache License' 20 | copyright = '2013 ' + authors_string 21 | url = 'http://mcloud.io/' 22 | -------------------------------------------------------------------------------- /mcloud/static_old/components/angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.27", 4 | "description": "HTML enhanced for web apps", 5 | "main": "angular.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/angular/angular.js.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "framework", 16 | "browser", 17 | "client-side" 18 | ], 19 | "author": "Angular Core Team ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/angular/angular.js/issues" 23 | }, 24 | "homepage": "http://angularjs.org" 25 | } 26 | -------------------------------------------------------------------------------- /mcloud/static/client/src/common/footer.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

© 2014 - Alessandro Arnodo. 4 |

5 |
6 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/addons/attach/attach.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Implements the attach method, that 3 | * attaches the terminal to a WebSocket stream. 4 | * 5 | * The bidirectional argument indicates, whether the terminal should 6 | * send data to the socket as well and is true, by default. 7 | */ 8 | Terminal.prototype.attach = function (socket, bidirectional) { 9 | var term = this; 10 | 11 | bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; 12 | this.socket = socket; 13 | 14 | socket.addEventListener('message', function (ev) { 15 | term.write(ev.data); 16 | }); 17 | 18 | if (bidirectional) { 19 | this.on('data', function (data) { 20 | socket.send(data); 21 | }); 22 | } 23 | } -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests in 2 | # multiple virtualenvs. This configuration file will run the test 3 | # suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | # 6 | # To run tox faster, check out Detox 7 | # (https://pypi.python.org/pypi/detox), which runs your tox runs in 8 | # parallel. To use it, "pip install detox" and then run "detox" from 9 | # this directory. 10 | 11 | [tox] 12 | envlist = py26,py27,py33,pypy,docs 13 | 14 | [testenv] 15 | deps = 16 | --no-deps 17 | --requirement 18 | {toxinidir}/requirements-dev.txt 19 | commands = paver test_all 20 | 21 | [testenv:docs] 22 | basepython = python 23 | commands = paver doc_html 24 | -------------------------------------------------------------------------------- /mcloud/plugin.py: -------------------------------------------------------------------------------- 1 | import inject 2 | from zope.interface import Interface 3 | from zope.interface.verify import verifyObject 4 | 5 | 6 | class IMcloudPlugin(Interface): 7 | """ 8 | Describes basic behavior of plugin. 9 | """ 10 | def setup(): 11 | """ 12 | Method called on plugin initialization. 13 | """ 14 | 15 | 16 | def enumerate_plugins(interface): 17 | try: 18 | plugins = inject.instance('plugins') 19 | except inject.InjectorException: 20 | return 21 | 22 | for plugin in plugins: 23 | try: 24 | adaptor = interface(plugin) 25 | if adaptor: 26 | verifyObject(interface, adaptor) 27 | yield adaptor 28 | except TypeError as e: 29 | print e -------------------------------------------------------------------------------- /mcloud/static/client/src/common/interceptors/httpInterceptor.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | function httpInterceptor($q, $log) { 5 | return { 6 | request: function(config) { 7 | return config; 8 | }, 9 | requestError: function(rejection) { 10 | $log.debug(rejection); 11 | return $q.reject(rejection); 12 | }, 13 | response: function(response) { 14 | $log.debug('response: ', response); 15 | return response; 16 | }, 17 | responseError: function(rejection) { 18 | $log.debug(rejection); 19 | return $q.reject(rejection); 20 | } 21 | }; 22 | } 23 | 24 | angular.module('common.interceptors.http', []) 25 | .factory('httpInterceptor', httpInterceptor); 26 | })(); 27 | -------------------------------------------------------------------------------- /mcloud/static_old/components/reconnectingWebsocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReconnectingWebSocket", 3 | "version": "0.0.0", 4 | "description": "A small JavaScript library that decorates the WebSocket API to provide a WebSocket connection that will automatically reconnect if the connection is dropped.", 5 | "main": "reconnecting-websocket.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/joewalnes/reconnecting-websocket" 12 | }, 13 | "author": "Joe Walnes", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/joewalnes/reconnecting-websocket/issues" 17 | }, 18 | "homepage": "https://github.com/joewalnes/reconnecting-websocket" 19 | } 20 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * term.js 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | */ 5 | 6 | var http = require('http') 7 | , path = require('path') 8 | , fs = require('fs'); 9 | 10 | var express = require('express') 11 | , term = require('../'); 12 | 13 | var app = express() 14 | , server = http.createServer(app); 15 | 16 | app.use(function(req, res, next) { 17 | var setHeader = res.setHeader; 18 | res.setHeader = function(name) { 19 | switch (name) { 20 | case 'Cache-Control': 21 | case 'Last-Modified': 22 | case 'ETag': 23 | return; 24 | } 25 | return setHeader.apply(res, arguments); 26 | }; 27 | next(); 28 | }); 29 | 30 | app.use(express.static(__dirname)); 31 | app.use(term.middleware()); 32 | 33 | server.listen(8080); 34 | -------------------------------------------------------------------------------- /mcloud/test_utils.py: -------------------------------------------------------------------------------- 1 | from decorator import contextmanager 2 | from flexmock import flexmock 3 | import inject 4 | from mcloud.txdocker import IDockerClient, DockerTwistedClient 5 | 6 | 7 | def fake_inject(services): 8 | def configurator(binder): 9 | for key, item in services.items(): 10 | binder.bind(key, item) 11 | inject.clear_and_configure(configurator) 12 | 13 | @contextmanager 14 | def real_docker(): 15 | def configurator(binder): 16 | binder.bind_to_constructor(IDockerClient, lambda: DockerTwistedClient()) 17 | inject.clear_and_configure(configurator) 18 | 19 | yield 20 | 21 | 22 | @contextmanager 23 | def mock_docker(): 24 | mock = flexmock(DockerTwistedClient()) 25 | inject.clear_and_configure(lambda binder: binder.bind(IDockerClient, mock)) 26 | 27 | yield mock 28 | 29 | 30 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/addons/fullscreen/fullscreen.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Fullscreen addon for xterm.js 3 | * 4 | * Implements the toggleFullscreen function. 5 | * 6 | * If the `fullscreen` argument has been supplied, then 7 | * if it is true, the fullscreen mode gets turned on, 8 | * if it is false or null, the fullscreen mode gets turned off. 9 | * 10 | * If the `fullscreen` argument has not been supplied, the 11 | * fullscreen mode is being toggled. 12 | */ 13 | Terminal.prototype.toggleFullscreen = function (fullscreen) { 14 | var fn; 15 | 16 | if (typeof fullscreen == 'undefined') { 17 | fn = (this.element.classList.contains('fullscreen')) ? 'remove' : 'add'; 18 | } else if (!fullscreen) { 19 | fn = 'remove'; 20 | } else { 21 | fn = 'add'; 22 | } 23 | 24 | this.element.classList[fn]('fullscreen'); 25 | } -------------------------------------------------------------------------------- /mcloud/static/client/test/unit/app/app.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint undef:false*/ 2 | (function() { 3 | 'use strict'; 4 | 5 | describe('app module', function() { 6 | var module; 7 | var deps; 8 | 9 | var hasModule = function(m) { 10 | return deps.indexOf(m) >= 0; 11 | }; 12 | 13 | beforeEach(function() { 14 | module = angular.module('app'); 15 | deps = module.value('app').requires; 16 | }); 17 | 18 | it('should be registered', function() { 19 | expect(module).not.toEqual(null); 20 | }); 21 | 22 | it('should have ui.router as a dependency', function() { 23 | expect(hasModule('ui.router')).toEqual(true); 24 | }); 25 | 26 | it('should have common.services.data as a dependency', function() { 27 | expect(hasModule('common.services.data')).toEqual(true); 28 | }); 29 | }); 30 | })(); 31 | -------------------------------------------------------------------------------- /mcloud/static/client/test/unit/common/directives/version.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint undef:false*/ 2 | (function() { 3 | 'use strict'; 4 | 5 | describe('Directive: version', function() { 6 | 7 | var compile; 8 | var rootScope; 9 | 10 | beforeEach(module('common.directives.version')); 11 | beforeEach(function() { 12 | module(function($provide) { 13 | //mocking version value 14 | $provide.value('version', 'test'); 15 | }); 16 | }); 17 | 18 | beforeEach(inject(function($compile, $rootScope) { 19 | compile = $compile; 20 | rootScope = $rootScope; 21 | })); 22 | 23 | it('should display the version number', function() { 24 | var element = compile('
')(rootScope); 25 | expect(element.html()).toMatch(/test/i); 26 | }); 27 | 28 | 29 | 30 | 31 | }); 32 | })(); 33 | -------------------------------------------------------------------------------- /mcloud/interrupt.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from twisted.internet.defer import inlineCallbacks 4 | 5 | 6 | class InterruptCancel(Exception): 7 | pass 8 | 9 | class InterruptManager(object): 10 | 11 | interrupt_stack = [] 12 | 13 | def append(self, handler): 14 | self.interrupt_stack.append(handler) 15 | 16 | @inlineCallbacks 17 | def handle_interrupt(self, manual=False, *args): 18 | try: 19 | last = None 20 | for itr in reversed(self.interrupt_stack): 21 | yield itr.interrupt(last) 22 | 23 | self.interrupt_stack.remove(itr) 24 | 25 | last = itr 26 | 27 | except InterruptCancel: 28 | pass 29 | 30 | def manual_interrupt(self): 31 | return self.handle_interrupt(manual=True) 32 | 33 | def register_interupt_handler(self): 34 | signal.signal(signal.SIGINT, self.handle_interrupt) 35 | -------------------------------------------------------------------------------- /docs/source/api_yml.rst: -------------------------------------------------------------------------------- 1 | 2 | mcloud.yml reference 3 | ========================== 4 | 5 | Example:: 6 | 7 | nginx: 8 | image: nginx 9 | 10 | memcache: 11 | image: jacksoncage/memcache 12 | 13 | app: 14 | build: local/dir 15 | volumes: 16 | local/dir/somedir: /var/data 17 | 18 | 19 | First level key is service name. In example above we have 20 | three services: nginx, memcache and app. 21 | 22 | 23 | On second level one of image or build is required. 24 | 25 | image 26 | docker container will be build from docker image found in docker registry by name 27 | 28 | build 29 | docker container will be build from docker image build using command like: 30 | docker build local/dir 31 | 32 | volumes is set of subdirectories that will be monted as subdirectories into docker container. 33 | local relative path on left side, path inside container on right side. 34 | -------------------------------------------------------------------------------- /mcloud/static/client/src/app/home/home.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * @name config 6 | * @description config block 7 | */ 8 | function config($stateProvider) { 9 | $stateProvider 10 | .state('root.home', { 11 | url: '/', 12 | views: { 13 | '@': { 14 | templateUrl: 'src/app/home/home.tpl.html', 15 | controller: 'HomeCtrl as home', 16 | resolve: { 17 | data: function(DataService) { 18 | return DataService.get(); 19 | } 20 | } 21 | } 22 | } 23 | }); 24 | } 25 | 26 | /** 27 | * @name HomeCtrl 28 | * @description Controller 29 | */ 30 | function HomeCtrl(data) { 31 | var home = this; 32 | home.data = data.data; 33 | } 34 | 35 | angular.module('home', []) 36 | .config(config) 37 | .controller('HomeCtrl', HomeCtrl); 38 | })(); 39 | -------------------------------------------------------------------------------- /mcloud/static/client/src/app/getting-started/getting-started.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * @name config 6 | * @description config block 7 | */ 8 | function config($stateProvider) { 9 | $stateProvider 10 | .state('root.getting-started', { 11 | url: '/getting-started', 12 | views: { 13 | '@': { 14 | templateUrl: 'src/app/getting-started/getting-started.tpl.html', 15 | controller: 'GettingStartedCtrl as docs' 16 | } 17 | } 18 | }); 19 | } 20 | 21 | /** 22 | * @name gettingStartedCtrl 23 | * @description Controller 24 | */ 25 | function GettingStartedCtrl($log) { 26 | var docs = this; 27 | docs.someMethos = function () { 28 | $log.debug('I\'m a method'); 29 | }; 30 | } 31 | 32 | angular.module('getting-started', []) 33 | .config(config) 34 | .controller('GettingStartedCtrl', GettingStartedCtrl); 35 | })(); 36 | -------------------------------------------------------------------------------- /mcloud/static/client/test/e2e/01.home.scenario.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*jshint undef:false */ 4 | (function() { 5 | var utils = require('../utils'); 6 | describe('app', function() { 7 | beforeEach(function() { 8 | console.info('\nrunning:', jasmine.getEnv().currentSpec.description); 9 | }); 10 | 11 | afterEach(function() { 12 | if (!jasmine.getEnv().currentSpec.results().passed()) { 13 | utils.takeScreenshot(browser, jasmine.getEnv().currentSpec.description.replace(/ /g, '-')); 14 | } 15 | }); 16 | 17 | it('should load the homepage', function() { 18 | browser.get('/'); 19 | expect(browser.isElementPresent(by.css('body'))).toBe(true); 20 | }); 21 | 22 | it('should navigate to the docs page when clicking', function() { 23 | element(by.css('a[ui-sref="root.getting-started"]')).click(); 24 | expect(browser.getCurrentUrl()).toMatch(/\/getting-started/); 25 | }); 26 | 27 | }); 28 | })(); 29 | -------------------------------------------------------------------------------- /mcloud/static/client/test/unit/app/home/home.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint undef:false*/ 2 | (function() { 3 | 'use strict'; 4 | 5 | describe('HomeCtrl', function() { 6 | var rootScope; 7 | var fakeData = ['some', 'data']; 8 | var ctrl; 9 | var scope; 10 | 11 | beforeEach(module('app')); 12 | beforeEach(inject(function($rootScope, $controller) { 13 | rootScope = $rootScope; 14 | scope = $rootScope.$new(); 15 | ctrl = $controller('HomeCtrl as home', { 16 | $scope: scope, 17 | data: { 18 | data: fakeData 19 | } 20 | }); 21 | })); 22 | 23 | it('should not be null', function() { 24 | expect(ctrl).not.toEqual(null); 25 | }); 26 | 27 | it('should have "data" into its $scope', function() { 28 | expect(scope.home.data[0]).toEqual('some'); 29 | expect(scope.home.data[1]).toEqual('data'); 30 | expect(scope.home.data.length).toEqual(2); 31 | }); 32 | }); 33 | })(); 34 | -------------------------------------------------------------------------------- /mcloud/static_old/components/reconnectingWebsocket/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reconnecting-websocket", 3 | "main": "reconnecting-websocket.js", 4 | "homepage": "https://github.com/joewalnes/reconnecting-websocket", 5 | "authors": [ 6 | "Joe Walnes", 7 | "headlessme " 8 | ], 9 | "description": "A small decorator for the JavaScript WebSocket API that automatically reconnects", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "_release": "592c441b27", 24 | "_resolution": { 25 | "type": "branch", 26 | "branch": "master", 27 | "commit": "592c441b27224600836ddb2975482665be5c0060" 28 | }, 29 | "_source": "git://github.com/joewalnes/reconnecting-websocket.git", 30 | "_target": "*", 31 | "_originalSource": "reconnectingWebsocket", 32 | "_direct": true 33 | } -------------------------------------------------------------------------------- /plugins/browser-auto-open/mcloud_browser_auto_open.py: -------------------------------------------------------------------------------- 1 | import inject 2 | from mcloud.plugin import IMcloudPlugin 3 | from mcloud.plugins import Plugin 4 | from mcloud.remote import ApiRpcServer 5 | from mcloud.service import IServiceLifecycleListener 6 | import os 7 | from twisted.internet.defer import inlineCallbacks 8 | from zope.interface import implements 9 | 10 | class BrowserAutoOpenPlugin(Plugin): 11 | implements(IMcloudPlugin, IServiceLifecycleListener) 12 | 13 | rpc_server = inject.attr(ApiRpcServer) 14 | dns_search_suffix = inject.attr('dns-search-suffix') 15 | 16 | @inlineCallbacks 17 | def on_service_start(self, service, ticket_id=None): 18 | if service.is_web(): 19 | domain = '%s.%s' % (service.app_name, self.dns_search_suffix) 20 | if ticket_id: 21 | yield self.rpc_server.task_progress('Launching web-browser: %s' % domain, ticket_id) 22 | 23 | os.system('sensible-browser %s' % domain) 24 | 25 | 26 | @inlineCallbacks 27 | def setup(self): 28 | yield None 29 | -------------------------------------------------------------------------------- /plugins/simple_publish/mcloud_simple_publish.py: -------------------------------------------------------------------------------- 1 | from mcloud.plugin import IMcloudPlugin 2 | from mcloud.plugins import Plugin 3 | from mcloud.service import IServiceBuilder 4 | from zope.interface import implements 5 | 6 | 7 | class SimplePublishPlugin(Plugin): 8 | implements(IMcloudPlugin, IServiceBuilder) 9 | 10 | def configure_container_on_create(self, service, config): 11 | if service.is_web(): 12 | 13 | if not 'PortBindings' in config: 14 | config['PortBindings'] = {} 15 | 16 | if not 'ExposedPorts' in config: 17 | config['ExposedPorts'] = {} 18 | 19 | config['PortBindings']['%s/tcp' % service.get_web_port()] = [ 20 | { 21 | "HostIp": "0.0.0.0", 22 | "HostPort": "80" 23 | } 24 | ] 25 | 26 | config['ExposedPorts'] = {'%s/tcp' % service.get_web_port(): {}} 27 | 28 | def configure_container_on_start(self, service, config): 29 | 30 | self.configure_container_on_create(service, config) 31 | 32 | def setup(self): 33 | pass -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/callout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /docs/source/use/debug.rst: -------------------------------------------------------------------------------- 1 | 2 | Debugging application 3 | ============================= 4 | 5 | 6 | Run 7 | ---------- 8 | 9 | Running command in container:: 10 | 11 | $ mcloud run service.app [command] 12 | 13 | Command will create copy of container, mount same volumes and execute command. 14 | 15 | Command is "bash" by default, which opens interactive terminal. 16 | 17 | Command may be omitted, by default bash is executed. 18 | 19 | This will create exact copy of container you are asking to run into and 20 | attach all volumes of this container. 21 | 22 | .. note:: 23 | As run command is just make an illusion of connecting into container, 24 | you can not see processes of target container, and you can't affect data that 25 | is located outside of volumes. 26 | 27 | 28 | 29 | Logs 30 | ------------ 31 | 32 | Show container logs:: 33 | 34 | $ mcloud logs service.app 35 | 36 | Show last 100 lines of container log and follow all new logs. 37 | Hit Ctrl+C for exit. 38 | 39 | 40 | Inspect 41 | ------------- 42 | 43 | Shows docker inspect for a container:: 44 | 45 | $ mcloud inspect service.app 46 | 47 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /mcloud/static_old/components/reconnectingWebsocket/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License: 2 | 3 | Copyright (c) 2010-2012, Joe Walnes 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 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Alex rudakov", 3 | "name": "mcloud-docs", 4 | "version": "1.0.0", 5 | "homepage": "https://mcloud.io", 6 | "description": "Mcloud documentation", 7 | "license": "MIT", 8 | 9 | "devDependencies": { 10 | "browser-sync": "~2.7.1", 11 | "del": "~1.1.1", 12 | "gulp": "~3.8.11", 13 | "gulp-changed": "~1.2.1", 14 | "gulp-concat": "~2.5.2", 15 | "gulp-csso": "~1.0.0", 16 | "gulp-header": "~1.2.2", 17 | "gulp-html2js": "~0.2.0", 18 | "gulp-if": "~1.2.5", 19 | "gulp-imagemin": "~2.2.1", 20 | "gulp-jshint": "~1.10.0", 21 | "gulp-load-plugins": "~0.10.0", 22 | "gulp-minify-html": "~1.0.2", 23 | "gulp-ng-annotate": "~0.5.3", 24 | "gulp-ng-html2js": "~0.2.0", 25 | "gulp-protractor": "1.0.0", 26 | "gulp-rev": "~3.0.1", 27 | "gulp-rev-replace": "~0.4.0", 28 | "gulp-sass": "~2.0.0", 29 | "gulp-size": "~1.2.1", 30 | "gulp-sourcemaps": "~1.5.2", 31 | "gulp-uglify": "~1.2.0", 32 | "gulp-useref": "~1.1.2", 33 | "lodash": "~3.8.0", 34 | "run-sequence": "~1.1.0" 35 | }, 36 | "engines": { 37 | "node": ">=0.10.x" 38 | }, 39 | "scripts": { 40 | "test": "gulp travis" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/test/bench.js: -------------------------------------------------------------------------------- 1 | /** 2 | * term.js 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | */ 5 | 6 | var element = { 7 | createElement: function() { return element; }, 8 | appendChild: function() {}, 9 | removeChild: function() {}, 10 | addEventListener: function() {}, 11 | removeEventListener: function() {}, 12 | setAttribute: function() {}, 13 | style: {} 14 | }; 15 | 16 | global.window = global; 17 | window.navigator = { userAgent: '' }; 18 | window.document = element; 19 | window.document.body = element; 20 | 21 | element.ownerDocument = window.document; 22 | window.document.defaultView = window; 23 | 24 | var Terminal = require('../src/term'); 25 | Terminal.cursorBlink = false; 26 | 27 | var data = require('./data').data; 28 | 29 | var term = new Terminal({ 30 | cols: 250, 31 | rows: 100 32 | }); 33 | 34 | term.open(element); 35 | 36 | var time = new Date; 37 | var t = 10; 38 | 39 | while (t--) { 40 | var l = data.length 41 | , i = 0; 42 | 43 | for (; i < l; i++) { 44 | term.write(data[i]); 45 | } 46 | } 47 | 48 | console.log('Completed: %d.', new Date - time); 49 | console.log('Average (?): 13.5k (for ~2.7k writes).'); 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Emacs rope configuration 2 | .env 3 | .ropeproject 4 | .project 5 | .pydevproject 6 | .settings 7 | 8 | # pyenv version file 9 | .python-version 10 | 11 | # Python 12 | *.py[co] 13 | 14 | ## Packages 15 | *.egg 16 | *.egg-info 17 | dist 18 | build 19 | eggs 20 | parts 21 | bin 22 | var 23 | sdist 24 | deb_dist 25 | develop-eggs 26 | .installed.cfg 27 | .env 28 | .idea 29 | 30 | ## Installer logs 31 | pip-log.txt 32 | 33 | ## Unit test / coverage reports 34 | .coverage 35 | .tox 36 | 37 | ## Translations 38 | *.mo 39 | 40 | ## paver generated files 41 | /paver-minilib.zip.cache 42 | .cache/ 43 | 44 | 45 | cachegrind.out.profilestats 46 | 47 | **/debian/files 48 | debian/mcloud.debhelper.log 49 | debian/mcloud.postinst.debhelper 50 | debian/mcloud.preinst.debhelper 51 | debian/mcloud.prerm.debhelper 52 | debian/mcloud.substvars 53 | debian/mcloud/ 54 | 55 | **/debian/mcloud-full.debhelper.log 56 | **/debian/mcloud-full.postinst.debhelper 57 | **/debian/mcloud-full.preinst.debhelper 58 | **/debian/mcloud-full.prerm.debhelper 59 | **/debian/mcloud-full.substvars 60 | **/debian/mcloud-full/ 61 | 62 | *.dsc 63 | *.tar.gz 64 | *.changes 65 | *.deb 66 | 67 | profilestats.prof 68 | 69 | node_modules/ 70 | 71 | 72 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | term.js test 3 | 9 |

term.js test

10 | 11 | 12 | 50 | -------------------------------------------------------------------------------- /mcloud/plugins/monitor.py: -------------------------------------------------------------------------------- 1 | import inject 2 | from mcloud.application import ApplicationController 3 | from mcloud.events import EventBus 4 | from mcloud.plugin import IMcloudPlugin 5 | from mcloud.plugins import Plugin 6 | from mcloud.txdocker import IDockerClient 7 | from twisted.internet import reactor 8 | from twisted.python import log 9 | from zope.interface import implements 10 | 11 | 12 | class DockerMonitorPlugin(Plugin): 13 | """ 14 | Monitors docker events and emmits "containers.updated" event when non-internal 15 | containers change their state. 16 | """ 17 | implements(IMcloudPlugin) 18 | 19 | client = inject.attr(IDockerClient) 20 | event_bus = inject.attr(EventBus) 21 | app_controller = inject.attr(ApplicationController) 22 | 23 | def setup(self): 24 | # reactor.callLater(0, self.attach_to_events) 25 | pass 26 | 27 | def on_event(self, event): 28 | if not self.app_controller.is_internal(event['id']): 29 | log.msg('New docker event: %s' % event) 30 | self.event_bus.fire_event('containers.updated', event) 31 | 32 | def attach_to_events(self, *args): 33 | log.msg('Start monitoring docker events') 34 | return self.client.events(self.on_event) 35 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, sourceLair Limited (https://github.com/sourcelair/) 2 | Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /mcloud/static_old/components/xterm.js/addons/attach/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 15 | 18 |
19 |
20 | 37 | 38 | -------------------------------------------------------------------------------- /docs/source/use/machine.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Using docker-machine inside mcloud 4 | ------------------------------------ 5 | 6 | Mcloud has integrated docker-machine now and it's available as "machine" command. 7 | 8 | Advantage over usual docker machine is that it's tightly integrated with mcloud deployments concept. 9 | Whenever you create command through machiine, it's automatically picked up 10 | by mcloud and available by application deployment. 11 | 12 | Another thing is that mcloud machine is running inside mcloud-server, it means configs 13 | are stored centrally on mcloud server. 14 | 15 | 16 | Example:: 17 | 18 | $ mcloud machine ls 19 | 20 | $ mcloud machine create -- --driver digitalocean test1 21 | 22 | .. note:: 23 | When using machine commands you need to insert "--". It's says that everything that goes after -- 24 | should be passed directly to docker machine and this is not mcloud's options. 25 | 26 | Reference for machine command is on docker site https://docs.docker.com/machine/#subcommands 27 | 28 | To set environment variables like DIGITALOCEAN_ACCESS_TOKEN and others, you can use set command:: 29 | 30 | $ mcloud set DIGITALOCEAN_ACCESS_TOKEN ffdksafjklsadjfkljsaklfja;kl 31 | 32 | $ mcloud vars 33 | 34 | Those variables will be accessible to docker-machine on the given mcloud server. -------------------------------------------------------------------------------- /mcloud/plugins/hosts.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import inject 3 | from mcloud.application import ApplicationController 4 | from mcloud.events import EventBus 5 | from mcloud.plugin import IMcloudPlugin 6 | from mcloud.plugins import Plugin 7 | from mcloud.service import IServiceBuilder 8 | from twisted.internet.defer import inlineCallbacks 9 | import txredisapi 10 | from twisted.python import log 11 | from zope.interface import implements 12 | 13 | 14 | class HostsPlugin(Plugin): 15 | implements(IMcloudPlugin, IServiceBuilder) 16 | 17 | eb = inject.attr(EventBus) 18 | app_controller = inject.attr(ApplicationController) 19 | redis = inject.attr(txredisapi.Connection) 20 | 21 | 22 | def configure_container_on_create(self, service, config): 23 | pass 24 | 25 | 26 | @inlineCallbacks 27 | def configure_container_on_start(self, service, config): 28 | if service.app_name: 29 | from mcloud.application import ApplicationController 30 | app_controller = inject.instance(ApplicationController) 31 | 32 | ip_list = yield app_controller.ip_list() 33 | 34 | if service.app_name in ip_list and len(ip_list[service.app_name]) > 0: 35 | config['ExtraHosts'] = ['%s:%s' % x for x in ip_list[service.app_name].items()] 36 | 37 | def setup(self): 38 | log.msg('Hosts plugin started') 39 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * term.js - an xterm emulator 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/term.js 5 | */ 6 | 7 | function term(options) { 8 | return new term.Terminal(options); 9 | } 10 | 11 | term.middleware = function(options) { 12 | var url = require('url'); 13 | return function(req, res, next) { 14 | if (url.parse(req.url).pathname !== '/term.js') { 15 | return next(); 16 | } 17 | 18 | if (+new Date(req.headers['if-modified-since']) === term.last) { 19 | res.statusCode = 304; 20 | res.end(); 21 | return; 22 | } 23 | 24 | res.writeHead(200, { 25 | 'Content-Type': 'application/javascript; charset=utf-8', 26 | 'Content-Length': Buffer.byteLength(term.script), 27 | 'Last-Modified': term.last 28 | }); 29 | 30 | res.end(term.script); 31 | }; 32 | }; 33 | 34 | term.path = __dirname + '/../src/term.js'; 35 | 36 | term.__defineGetter__('script', function() { 37 | if (term._script) return term._script; 38 | term.last = +new Date; 39 | return term._script = require('fs').readFileSync(term.path, 'utf8'); 40 | }); 41 | 42 | term.__defineGetter__('Terminal', function() { 43 | if (term._Terminal) return term._Terminal; 44 | return term._Terminal = require('../src/term'); 45 | }); 46 | 47 | /** 48 | * Expose 49 | */ 50 | 51 | module.exports = term; 52 | -------------------------------------------------------------------------------- /mcloud/static/client/src/app/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.element(document).ready(function() { 5 | angular.bootstrap(document, ['app']); 6 | }); 7 | 8 | function config($stateProvider, $urlRouterProvider, $logProvider, $httpProvider) { 9 | $urlRouterProvider.otherwise('/'); 10 | $logProvider.debugEnabled(true); 11 | $httpProvider.interceptors.push('httpInterceptor'); 12 | $stateProvider 13 | .state('root', { 14 | views: { 15 | 'header': { 16 | templateUrl: 'src/common/header.tpl.html', 17 | controller: 'HeaderCtrl' 18 | }, 19 | 'footer': { 20 | templateUrl: 'src/common/footer.tpl.html', 21 | controller: 'FooterCtrl' 22 | } 23 | } 24 | }); 25 | } 26 | 27 | function MainCtrl($log) { 28 | $log.debug('MainCtrl laoded!'); 29 | } 30 | 31 | function run($log) { 32 | $log.debug('App is running!'); 33 | } 34 | 35 | angular.module('app', [ 36 | 'ui.router', 37 | 'home', 38 | 'getting-started', 39 | 'common.header', 40 | 'common.footer', 41 | 'common.services.data', 42 | 'common.directives.version', 43 | 'common.filters.uppercase', 44 | 'common.interceptors.http', 45 | 'templates', 46 | 'lumx' 47 | ]) 48 | .config(config) 49 | .run(run) 50 | .controller('MainCtrl', MainCtrl) 51 | .value('version', '1.1.0'); 52 | })(); 53 | -------------------------------------------------------------------------------- /docs/source/use/volumes.rst: -------------------------------------------------------------------------------- 1 | 2 | Volume synchronization 3 | =========================== 4 | 5 | Volume commands are about controlling the service volumes and data synchronization. 6 | 7 | 8 | Syntax 9 | ----------- 10 | 11 | Synchronize volumes and folders. Syntax is:: 12 | 13 | $ mcloud {from} {to} [--no-remove] [--force] 14 | 15 | From and to are volume spec. 16 | Spec for remote volume:: 17 | 18 | [service.]app@host[:/volume/path] 19 | 20 | host may be set to "@me" which is current host. 21 | service and volume name may be skipped, then command assumes it's main volume of application (where mcloud.yml resides) 22 | 23 | If volume spec do not match remote volume format, then command assumes, it is 24 | just directory name. 25 | 26 | 27 | Work order 28 | -------------- 29 | 30 | #. Command computes snapshot of source and destination locations by collecting list of files, 31 | calculating modification time diffs. (time diff = server current time - modification time) 32 | #. Compares result, and if no --force flag, shows diff list to user. (new, updated, removed files) 33 | #. if no --force flag, ask confirmation from user 34 | #. Create archive with new and updated files 35 | #. Transfer archive (progress is displayed) 36 | #. Extract archive 37 | #. if no --no-remove flag, removes files. 38 | 39 | 40 | Usage patterns 41 | ---------------- 42 | 43 | - local folder to local folder 44 | - remote volume to local folder 45 | - local folder to remote volume 46 | - remote volume to remote volume 47 | 48 | -------------------------------------------------------------------------------- /docs/source/use/remote.rst: -------------------------------------------------------------------------------- 1 | 2 | =============================== 3 | Working with remote mcloud 4 | =============================== 5 | 6 | Initial application deployment 7 | =============================== 8 | 9 | Test you have connection with your server:: 10 | 11 | $ mcloud -h list 12 | 13 | If, you see empty table from remote server, then it's working. 14 | 15 | We can initialize a nwe application. 16 | 17 | Go to folder with your application (we will use flask-redis) example. 18 | And initialize application on remote machine:: 19 | 20 | $ mcloud -h init flask-redis mcloud.yml 21 | 22 | flask-redis is name of your application on remote server. 23 | 24 | MCloud will create a new application there. Now you can push initial version 25 | of your application to remote server:: 26 | 27 | $ mcloud sync . flask-redis@ 28 | 29 | During sync mcloud will ask to review changes and confirmation to de[ploy the things. 30 | 31 | As application is synced, you can start it:: 32 | 33 | $ mcloud -h start 34 | 35 | 36 | Updating deployed application 37 | ======================================= 38 | 39 | Updating applications is as easy as uploading changes:: 40 | 41 | $ mcloud sync . flask-redis@ 42 | 43 | And restarting application:: 44 | 45 | $ mcloud -h restart flask-redis 46 | 47 | If you changed configuration in mcloud.yml, you may need to deploy changes to 48 | container structure as well:: 49 | 50 | $ mcloud -h config --update flask-redis 51 | 52 | -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | term.js 3 | 7 | 29 |

term.js

30 | 31 | 32 | 67 | -------------------------------------------------------------------------------- /docs/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./build.config.js'); 4 | var gulp = require('gulp'); 5 | var $ = require('gulp-load-plugins')(); 6 | var runSequence = require('run-sequence'); 7 | var browserSync = require('browser-sync'); 8 | var reload = browserSync.reload; 9 | var pkg = require('./package'); 10 | var del = require('del'); 11 | var _ = require('lodash'); 12 | /* jshint camelcase:false*/ 13 | var exec = require('child_process').exec; 14 | 15 | 16 | //build files for development 17 | gulp.task('build', [], function(cb) { 18 | 19 | exec('make html', function(err) { 20 | if (err) return cb(err); // return error 21 | cb(); // finished task 22 | }); 23 | 24 | }); 25 | //build files for development 26 | gulp.task('build_site', [], function(cb) { 27 | 28 | exec('jekyll build', {cwd: 'source/themes/cloud.modera.org/jekyll'}, function(err) { 29 | if (err) return cb(err); // return error 30 | cb(); // finished task 31 | }); 32 | 33 | }); 34 | 35 | //default task 36 | gulp.task('default', ['serve']); // 37 | 38 | //gulp.task('python_server', bg("./.env/bin/cratis", ["runserver", '0.0.0.0:8000'])); 39 | 40 | gulp.task('serve', ['build_site', 'build'], function() { 41 | browserSync({ 42 | notify: false, 43 | ghostMode: false, 44 | logPrefix: pkg.name, 45 | server: [ 46 | // 'build/html', 47 | 'source/themes/cloud.modera.org/public' 48 | ] 49 | }); 50 | 51 | gulp.watch(config.source, ['build', reload]); 52 | gulp.watch(config.site_doc_theme, ['build', reload]); 53 | gulp.watch(config.site_source, ['build_site', 'build', reload]); 54 | }); 55 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # import librsync 5 | # 6 | # s = librsync.signature(file('file1', 'rb')) 7 | # 8 | # with open('delta', 'w') as f: 9 | # delta = librsync.delta(file('file2', 'rb'), s) 10 | # f.write(delta.read()) 11 | 12 | # librsync.patch(file('file1', 'rb'), delta, file('file3', 'wb')) 13 | # 14 | # import tarfile 15 | # with tarfile.open("sample.tar", "w") as tar: 16 | # 17 | # prev = None 18 | # for root, dirs, files in os.walk("/home/alex/dev/grandex/lib"): 19 | # # print root 20 | # # print dir 21 | # # print files 22 | # # root = root[len("/home/alex/dev/grandex/lib") + 1:] 23 | # 24 | # # for dirname in dirs: 25 | # # print os.path.join(root, dirname) 26 | # 27 | # for filename in files: 28 | # path_ = os.path.join(root, filename) 29 | # 30 | # if path_.endswith('.pyc'): 31 | # continue 32 | # 33 | # src = file(path_, 'rb') 34 | # 35 | # 36 | # print path_ 37 | # signature = librsync.signature(src) 38 | # 39 | # if prev: 40 | # delta = librsync.delta(prev, signature) 41 | # print len(delta.read()) 42 | # 43 | # prev = src 44 | # 45 | # # 46 | # # break 47 | # 48 | # # tar.addfile(tar.gettarinfo(arcname=path_, fileobj=delta)) 49 | # # tar.addfile(tar.gettarinfo(arcname=path_, fileobj=signature)) 50 | # 51 | # # print "" 52 | # # for items in fnmatch.filter(files, "*"): 53 | # # print "..." + items 54 | # # print "" 55 | # 56 | # # tar.close() -------------------------------------------------------------------------------- /docs/source/dev.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Developing mcloud 4 | ----------------------- 5 | 6 | Easiest way to develop mcloud is to mount volume that contains mcloud source into the container:: 7 | 8 | MacOS:: 9 | 10 | docker run -d --restart always -v /Users:/Users -v /var/run/docker.sock:/var/run/docker.sock -v /Users/alex/dev/mcloud/mcloud:/opt/mcloud/local/lib/python2.7/site-packages/mcloud --name mcloud mcloud/mcloud 11 | 12 | Linux:: 13 | 14 | docker run -d --restart always -v /home:/home -v /var/run/docker.sock:/var/run/docker.sock -v /home/alex/dev/mcloud/mcloud:/opt/mcloud/local/lib/python2.7/site-packages/mcloud --name mcloud mcloud/mcloud 15 | 16 | 17 | This way you can edit mcloud source and see results. 18 | 19 | You also may create client containser and execute mcloud client from there:: 20 | 21 | $ docker run -i -t --volumes-from mcloud --link mcloud --rm -w `pwd` mcloud/mcloud bash 22 | 23 | $ mcloud list 24 | 25 | Another usefull trick is to execute mcloud-server manualy and install mcloud manually:: 26 | 27 | # create container 28 | $ docker run -d -v /Users:/Users -v /var/run/docker.sock:/var/run/docker.sock -v /Users/alex/dev/mcloud:/opt/mcloud-src --name mcloud mcloud/mcloud 29 | $ /opt/mcloud/bin/pip install -e /opt/mcloud-src 30 | 31 | # attach to container 32 | $ docker exec -i -t mcloud bash 33 | 34 | # inside continer: 35 | $ supervisorctl stop mcloud 36 | $ mcloud-server 37 | 38 | 39 | Generating new version 40 | -------------------------- 41 | 42 | Process is folllowing:: 43 | 44 | $ bumpversion minor 45 | 46 | # ... or... 47 | 48 | $ bumpversion patch 49 | 50 | 51 | $ python setup.py sdist register upload 52 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | 2 | from fabric.context_managers import lcd, settings 3 | from fabric.operations import local, run 4 | from fabric.state import env 5 | 6 | from mcloud.version import version 7 | import os 8 | 9 | env.hosts = ['root@dev1.cloud.modera.org'] 10 | 11 | def publish_plugin(name=None): 12 | with lcd('plugins/%s' % name): 13 | local('python setup.py sdist register upload') 14 | 15 | def publish(type='patch'): 16 | local('bumpversion %s' % type) 17 | local('python setup.py sdist register upload') 18 | 19 | for plugin_name in [f for f in os.listdir('plugins') if os.path.isdir('plugins/%s' % f) and f != 'tpl']: 20 | print 'Publishing %s' % plugin_name 21 | with lcd('plugins/%s' % plugin_name): 22 | local('python setup.py sdist register upload') 23 | local('pip install -e .') 24 | 25 | local('git push') 26 | local('git push --tags') 27 | local('pip install -e .') 28 | 29 | 30 | 31 | def new_plugin(name): 32 | os.chdir('plugins') 33 | cookiecutter('tpl', no_input=True, extra_context={ 34 | 'name': name, 35 | 'version': version 36 | }) 37 | 38 | def docs(): 39 | with lcd('docs/source/themes/cloud.modera.org'): 40 | local('git add .') 41 | with settings(warn_only=True): 42 | local('git commit') 43 | local('git push') 44 | 45 | local('git add docs/source/themes/cloud.modera.org') 46 | local('git add docs') 47 | local('git commit') 48 | local('git push') 49 | 50 | 51 | def deploy_dev1(): 52 | # publish() 53 | run('/opt/mcloud/bin/pip install mcloud -U') 54 | run('/opt/mcloud/bin/pip install mcloud-plugin-haproxy -U') 55 | run('service mcloud restart') 56 | -------------------------------------------------------------------------------- /plugins/haproxy/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | from setuptools import setup, find_packages 4 | 5 | # See here for more options: 6 | # 7 | 8 | mcloud_version = '1.0.3' 9 | 10 | setup( 11 | name='mcloud-plugin-haproxy', 12 | version=mcloud_version, 13 | author='Alex Rudakov', 14 | author_email='ribozz@gmail.com', 15 | maintainer='Alex Rudakov', 16 | maintainer_email='ribozz@gmail.com', 17 | url='mcloud.io', 18 | description='Haproxy integration for mcloud', 19 | long_description=open('README.rst').read(), 20 | 21 | classifiers=[ 22 | 'Development Status :: 4 - Beta', 23 | 'Environment :: Console', 24 | 'Intended Audience :: Developers', 25 | 'Intended Audience :: System Administrators', 26 | 'License :: OSI Approved :: Apache Software License', 27 | 'Natural Language :: English', 28 | 'Operating System :: OS Independent', 29 | 'Programming Language :: Python :: 2.6', 30 | 'Programming Language :: Python :: 2.7', 31 | 'Programming Language :: Python :: 3.3', 32 | 'Programming Language :: Python :: Implementation :: PyPy', 33 | 'Topic :: Documentation', 34 | 'Topic :: Software Development :: Libraries :: Python Modules', 35 | 'Topic :: System :: Installation/Setup', 36 | 'Topic :: System :: Software Distribution', 37 | ], 38 | py_modules=['mcloud_haproxy'], 39 | install_requires=[ 40 | 'mcloud>=%s' % mcloud_version, 41 | 'jinja2', 42 | 'PyYAML' 43 | ], 44 | 45 | entry_points={ 46 | 'mcloud_plugins': [ 47 | 'haproxy = mcloud_haproxy:HaproxyPlugin' 48 | ] 49 | } 50 | ) 51 | -------------------------------------------------------------------------------- /plugins/simple_publish/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | mcloud_version = '0.10.10' 4 | 5 | from setuptools import setup, find_packages 6 | 7 | # See here for more options: 8 | # 9 | setup( 10 | name='mcloud-plugin-simple-publish', 11 | version=mcloud_version, 12 | author='Alex Rudakov', 13 | author_email='ribozz@gmail.com', 14 | maintainer='Alex Rudakov', 15 | maintainer_email='ribozz@gmail.com', 16 | url='mcloud.io', 17 | description='Very simple mechanism to publish application using docker-proxy', 18 | long_description=open('README.rst').read(), 19 | 20 | classifiers=[ 21 | 'Development Status :: 4 - Beta', 22 | 'Environment :: Console', 23 | 'Intended Audience :: Developers', 24 | 'Intended Audience :: System Administrators', 25 | 'License :: OSI Approved :: Apache Software License', 26 | 'Natural Language :: English', 27 | 'Operating System :: OS Independent', 28 | 'Programming Language :: Python :: 2.6', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Programming Language :: Python :: 3.3', 31 | 'Programming Language :: Python :: Implementation :: PyPy', 32 | 'Topic :: Documentation', 33 | 'Topic :: Software Development :: Libraries :: Python Modules', 34 | 'Topic :: System :: Installation/Setup', 35 | 'Topic :: System :: Software Distribution', 36 | ], 37 | py_modules=['mcloud_simple_publish'], 38 | install_requires=[ 39 | 'mcloud==%s' % mcloud_version, 40 | ], 41 | 42 | entry_points={ 43 | 'mcloud_plugins': [ 44 | 'simple_publish = mcloud_simple_publish:SimplePublishPlugin' 45 | ] 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /plugins/haproxy/README.rst: -------------------------------------------------------------------------------- 1 | Haproxy publishing 2 | ============================ 3 | 4 | Haproxy plugin install haproxy as a load balancer for mcloud. Plugin is useful when you need 5 | deploy multiple applications on one server, or use complex application publishing 6 | 7 | 8 | Multiple applications 9 | ------------------------- 10 | 11 | .. uml:: 12 | 13 | cloud Internet { 14 | 15 | } 16 | 17 | package Docker { 18 | [Haproxy] << Load Balancer >> 19 | 20 | database App1 { 21 | [nginx.myapp] 22 | [another.myapp] 23 | [something.myapp] 24 | } 25 | 26 | database App2 { 27 | [nginx.another] 28 | [another.another] 29 | } 30 | 31 | Haproxy ..> nginx.myapp 32 | Haproxy ..> nginx.another 33 | 34 | } 35 | 36 | Internet ..> Haproxy 37 | 38 | 39 | Multiple versions 40 | ------------------------- 41 | 42 | 43 | .. uml:: 44 | 45 | cloud Internet { 46 | 47 | } 48 | 49 | package Docker { 50 | [Haproxy] << Load Balancer >> 51 | 52 | [nodejs.app_v1] 53 | [nodejs.app_v2] 54 | 55 | Haproxy ..> nodejs.app_v1 56 | Haproxy -> nodejs.app_v2 57 | } 58 | 59 | Internet ..> Haproxy 60 | 61 | 62 | Haproxy template 63 | ----------------------- 64 | 65 | You can use your own template by placing it in /root/.mcloud/haproxy.tpl. Mcloud kindly places default config there. 66 | 67 | .. highlights:: 68 | 69 | Template is Jinja2 template http://jinja.pocoo.org/docs/ 70 | 71 | To apply your changes to template restart mcloud:: 72 | 73 | $ docker restart mcloud 74 | 75 | Default tamplate 76 | ^^^^^^^^^^^^^^^^^^ 77 | 78 | .. literalinclude:: mcloud_haproxy.py 79 | :lines: 21-94 80 | :language: jinja 81 | 82 | 83 | -------------------------------------------------------------------------------- /docs/source/use/shell.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ModeraCloud shell 5 | =================== 6 | 7 | ModeraCloud shell simplify usage of remote deployments, and as well may be used 8 | locally to shorten commands, if you mostly work with one application. 9 | 10 | Shell saves you time in following: 11 | 12 | - you don't need to type mcloud prefix every time. 13 | - command execution is faster (especially if you execute commands remotely). 14 | - "use" command allows to not type host name or application name. 15 | 16 | 17 | Start shell 18 | -------------- 19 | 20 | :: 21 | 22 | $ mcloud shell 23 | 24 | 25 | Exit shell 26 | -------------- 27 | 28 | Shell will not react to Ctrl+C command. Instead you should type "exit" or 29 | hit Ctrl+D. 30 | 31 | 32 | Use application 33 | ---------------------- 34 | 35 | *use* command allow to omit application name, when executing some commands:: 36 | 37 | mcloud> use myapp 38 | 39 | mcloud> status 40 | mcloud> start 41 | mcloud> stop 42 | 43 | 44 | System commands 45 | ------------------------- 46 | 47 | Anything that starts with "!" is executed as system command:: 48 | 49 | mcloud> !ls 50 | 51 | ... list files ... 52 | 53 | If you need to execute set of commands, you can run bash:: 54 | 55 | mcloud> !bash 56 | 57 | To exit, just hit Ctrl+D, and you are back in ModeraCloud shell. If you messed up, see `http://www.imdb.com/title/tt1375666/` for reference. 58 | 59 | 60 | Switching to remote server 61 | ---------------------------- 62 | 63 | If you need to execute commands on remote machine, isue use command with host:: 64 | 65 | mcloud> use @myserver.com 66 | 67 | You can combine use remote server with application name:: 68 | 69 | mcloud> use myapp@myserver.com 70 | 71 | To switch back to local server:: 72 | 73 | mcloud> use @ 74 | -------------------------------------------------------------------------------- /tests/test_txdocker.py: -------------------------------------------------------------------------------- 1 | from flexmock import flexmock 2 | from mcloud import txhttp 3 | from mcloud.test_utils import real_docker, mock_docker 4 | from mcloud.txdocker import DockerTwistedClient, DockerConnectionFailed 5 | import pytest 6 | from twisted.internet import defer 7 | 8 | 9 | @pytest.fixture 10 | def client(): 11 | with mock_docker(): 12 | client = DockerTwistedClient(url='unix://var/run/docker.sock/') 13 | return client 14 | 15 | 16 | @pytest.inlineCallbacks 17 | def test_request(client): 18 | """ 19 | @type client: DockerTwistedClient 20 | """ 21 | 22 | def test(url, **kwargs): 23 | assert url == 'unix://var/run/docker.sock//boooooo' 24 | 25 | assert 'data' in kwargs 26 | assert kwargs['data'] == {'foo': 'bar'} 27 | assert 'headers' in kwargs 28 | assert kwargs['headers'] == {'x': 'boo'} 29 | 30 | return defer.succeed('foobar') 31 | 32 | result = yield client._request('boooooo', data={'foo': 'bar'}, response_handler=None, headers={'x': 'boo'}, method=test) 33 | 34 | assert result == 'foobar' 35 | 36 | 37 | def test_get(client): 38 | 39 | flexmock(client) 40 | client.should_receive('_request').with_args(url='foo?foo=bar&boo=1', method=txhttp.get, foo='bar').once().and_return('baz') 41 | 42 | assert client._get('foo', foo='bar', data={'foo': 'bar', 'boo': 1}) == 'baz' 43 | 44 | 45 | def test_post(client): 46 | 47 | flexmock(client) 48 | client.should_receive('_request').with_args(url='foo', method=txhttp.post, foo='bar').once().and_return('baz') 49 | 50 | assert client._post('foo', foo='bar') == 'baz' 51 | 52 | 53 | def test_delete(client): 54 | 55 | flexmock(client) 56 | client.should_receive('_request').with_args(url='foo', method=txhttp.delete, foo='bar').once().and_return('baz') 57 | 58 | assert client._delete('foo', foo='bar') == 'baz' 59 | 60 | -------------------------------------------------------------------------------- /mcloud/static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Alessandro Arnodo", 3 | "name": "angular-kickstart", 4 | "email": "alessandro@arnodo.net", 5 | "url": "http://alessandro.arnodo.net", 6 | "version": "1.1.0", 7 | "homepage": "https://github.com/vesparny/angular-kickstart", 8 | "description": "Speed up your AngularJS development whith a great build system.", 9 | "license": "MIT", 10 | "bugs": "https://github.com/vesparny/angular-kickstart/issues", 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:vesparny/angular-kickstart.git" 14 | }, 15 | "devDependencies": { 16 | "browser-sync": "~2.7.1", 17 | "del": "~1.1.1", 18 | "gulp": "~3.8.11", 19 | "gulp-changed": "~1.2.1", 20 | "gulp-concat": "~2.5.2", 21 | "gulp-csso": "~1.0.0", 22 | "gulp-header": "~1.2.2", 23 | "gulp-html2js": "~0.2.0", 24 | "gulp-if": "~1.2.5", 25 | "gulp-imagemin": "~2.2.1", 26 | "gulp-jshint": "~1.10.0", 27 | "gulp-load-plugins": "~0.10.0", 28 | "gulp-minify-html": "~1.0.2", 29 | "gulp-ng-annotate": "~0.5.3", 30 | "gulp-ng-html2js": "~0.2.0", 31 | "gulp-protractor": "1.0.0", 32 | "gulp-rev": "~3.0.1", 33 | "gulp-rev-replace": "~0.4.0", 34 | "gulp-sass": "~2.0.0", 35 | "gulp-size": "~1.2.1", 36 | "gulp-sourcemaps": "~1.5.2", 37 | "gulp-uglify": "~1.2.0", 38 | "gulp-useref": "~1.1.2", 39 | "jshint-stylish": "~1.0.2", 40 | "karma": "~0.12.31", 41 | "karma-chrome-launcher": "~0.1.8", 42 | "karma-coverage": "~0.3.1", 43 | "karma-html-reporter": "~0.2.6", 44 | "karma-jasmine": "~0.3.5", 45 | "karma-mocha-reporter": "~1.0.2", 46 | "karma-phantomjs-launcher": "~0.1.4", 47 | "lodash": "~3.8.0", 48 | "protractor": "~2.0.0", 49 | "run-sequence": "~1.1.0" 50 | }, 51 | "engines": { 52 | "node": ">=0.10.x" 53 | }, 54 | "scripts": { 55 | "test": "gulp travis" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/test_txdocker_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | from mcloud.attach import attach_to_container 3 | import os 4 | from flexmock import flexmock 5 | from mcloud import txhttp 6 | from mcloud.container import DockerfileImageBuilder 7 | from mcloud.test_utils import real_docker 8 | from mcloud.txdocker import DockerTwistedClient 9 | import pytest 10 | import re 11 | from twisted.internet import defer 12 | 13 | @pytest.fixture 14 | def client(): 15 | with real_docker(): 16 | client = DockerTwistedClient() 17 | return client 18 | 19 | 20 | @pytest.inlineCallbacks 21 | def test_images(client): 22 | 23 | result = yield client.images(name='image_that_do_not_exist_ever_326782387') 24 | assert result == [] 25 | 26 | @pytest.inlineCallbacks 27 | def test_images_that_exist(client): 28 | 29 | result = yield client.images(name='ubuntu') 30 | assert result[0]['RepoTags'][0].startswith('ubuntu:') 31 | 32 | @pytest.inlineCallbacks 33 | def test_images_all(client): 34 | 35 | result = yield client.images() 36 | 37 | assert len(result) > 1 38 | 39 | @pytest.inlineCallbacks 40 | @pytest.mark.xfail 41 | def test_build(client): 42 | 43 | class Publisher(object): 44 | 45 | def __init__(self): 46 | self.called = 0 47 | 48 | def publish(self, data, tag): 49 | assert tag == 'log-123123' 50 | assert len(data) > 0 51 | 52 | self.called += 1 53 | 54 | client.message_publisher = Publisher() 55 | 56 | builder = DockerfileImageBuilder(os.path.join(os.path.dirname(__file__), '_files/ct_bash')) 57 | 58 | d = builder.create_archive() 59 | 60 | def build_image(docker_file): 61 | return client.build_image(docker_file, ticket_id=123123) 62 | 63 | d.addCallback(build_image) 64 | 65 | result = yield d 66 | 67 | assert re.match('^[0-9a-f]+$', result) 68 | 69 | assert client.message_publisher.called > 0 70 | -------------------------------------------------------------------------------- /plugins/browser-auto-open/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | import sys 5 | import imp 6 | 7 | metadata = imp.load_source( 8 | 'metadata', os.path.join('../../mcloud', 'metadata.py')) 9 | 10 | from setuptools import setup, find_packages 11 | 12 | # See here for more options: 13 | # 14 | 15 | mcloud_version = '0.10.14' 16 | 17 | setup( 18 | name='mcloud-plugin-browser-auto-open', 19 | version=mcloud_version, 20 | author='Alex Rudakov', 21 | author_email='ribozz@gmail.com', 22 | maintainer='Alex Rudakov', 23 | maintainer_email='ribozz@gmail.com', 24 | url='mcloud.io', 25 | description='browser-auto-open mcloud plugin', 26 | long_description=open('README.rst').read(), 27 | 28 | classifiers=[ 29 | 'Development Status :: 4 - Beta', 30 | 'Environment :: Console', 31 | 'Intended Audience :: Developers', 32 | 'Intended Audience :: System Administrators', 33 | 'License :: OSI Approved :: Apache Software License', 34 | 'Natural Language :: English', 35 | 'Operating System :: OS Independent', 36 | 'Programming Language :: Python :: 2.6', 37 | 'Programming Language :: Python :: 2.7', 38 | 'Programming Language :: Python :: 3.3', 39 | 'Programming Language :: Python :: Implementation :: PyPy', 40 | 'Topic :: Documentation', 41 | 'Topic :: Software Development :: Libraries :: Python Modules', 42 | 'Topic :: System :: Installation/Setup', 43 | 'Topic :: System :: Software Distribution', 44 | ], 45 | py_modules=['mcloud_browser_auto_open'], 46 | install_requires=[ 47 | 'mcloud==%s' % mcloud_version, 48 | 'PyYAML' 49 | ], 50 | 51 | entry_points={ 52 | 'mcloud_plugins': [ 53 | 'browser-auto-open = mcloud_browser_auto_open:BrowserAutoOpenPlugin' 54 | ] 55 | } 56 | ) 57 | -------------------------------------------------------------------------------- /mcloud/static_old/components/reconnectingWebsocket/README.md: -------------------------------------------------------------------------------- 1 | ReconnectingWebSocket 2 | ===================== 3 | 4 | A small JavaScript library that decorates the WebSocket API to provide 5 | a WebSocket connection that will automatically reconnect if the 6 | connection is dropped. 7 | 8 | It is API compatible, so when you have: 9 | 10 | ws = new WebSocket('ws://....'); 11 | 12 | you can replace with: 13 | 14 | ws = new ReconnectingWebSocket('ws://....'); 15 | 16 | Minified library with gzip compression is less than 600 bytes. 17 | 18 | How reconnections occur 19 | ----------------------- 20 | 21 | With the standard `WebSocket` API, the events you receive from the WebSocket instance are typically: 22 | 23 | onopen 24 | onmessage 25 | onmessage 26 | onmessage 27 | onclose // At this point the WebSocket instance is dead. 28 | 29 | With a `ReconnectingWebSocket`, after an `onclose` event is called it will automatically attempt to reconnect. In addition, a connection is attempted repeatedly (with a small pause) until it succeeds. So the events you receive may look something more like: 30 | 31 | onopen 32 | onmessage 33 | onmessage 34 | onmessage 35 | onclose 36 | // ReconnectingWebSocket attempts to reconnect 37 | onopen 38 | onmessage 39 | onmessage 40 | onmessage 41 | onclose 42 | // ReconnectingWebSocket attempts to reconnect 43 | onopen 44 | onmessage 45 | onmessage 46 | onmessage 47 | onclose 48 | 49 | This is all handled automatically for you by the library. 50 | 51 | More 52 | ---- 53 | 54 | Like this? Check out [websocketd](https://github.com/joewalnes/websocketd) for the simplest way to create WebSocket backends from any programming language. 55 | 56 | [Follow @joewalnes](https://twitter.com/joewalnes) 57 | 58 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/joewalnes/reconnecting-websocket/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 59 | 60 | -------------------------------------------------------------------------------- /plugins/tpl/{{cookiecutter.name}}/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | import sys 5 | import imp 6 | 7 | metadata = imp.load_source( 8 | 'metadata', os.path.join('../../mcloud', 'metadata.py')) 9 | 10 | from setuptools import setup, find_packages 11 | 12 | # See here for more options: 13 | # 14 | 15 | mcloud_version = '{{cookiecutter.version}}' 16 | 17 | setup( 18 | name='mcloud-plugin-{{cookiecutter.name}}', 19 | version=mcloud_version, 20 | author='Alex Rudakov', 21 | author_email='ribozz@gmail.com', 22 | maintainer='Alex Rudakov', 23 | maintainer_email='ribozz@gmail.com', 24 | url='mcloud.io', 25 | description='{{cookiecutter.name}} mcloud plugin', 26 | long_description=open('README.rst').read(), 27 | 28 | classifiers=[ 29 | 'Development Status :: 4 - Beta', 30 | 'Environment :: Console', 31 | 'Intended Audience :: Developers', 32 | 'Intended Audience :: System Administrators', 33 | 'License :: OSI Approved :: Apache Software License', 34 | 'Natural Language :: English', 35 | 'Operating System :: OS Independent', 36 | 'Programming Language :: Python :: 2.6', 37 | 'Programming Language :: Python :: 2.7', 38 | 'Programming Language :: Python :: 3.3', 39 | 'Programming Language :: Python :: Implementation :: PyPy', 40 | 'Topic :: Documentation', 41 | 'Topic :: Software Development :: Libraries :: Python Modules', 42 | 'Topic :: System :: Installation/Setup', 43 | 'Topic :: System :: Software Distribution', 44 | ], 45 | py_modules=['mcloud_{{cookiecutter.name}}'], 46 | install_requires=[ 47 | 'mcloud==%s' % mcloud_version, 48 | 'PyYAML' 49 | ], 50 | 51 | entry_points={ 52 | 'mcloud_plugins': [ 53 | '{{cookiecutter.name}} = mcloud_{{cookiecutter.name}}:{{cookiecutter.name|capitalize}}Plugin' 54 | ] 55 | } 56 | ) 57 | -------------------------------------------------------------------------------- /docs/source/os_macos.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | Usage on macos 4 | ========================== 5 | 6 | mcloud start 7 | Using folder name as application name: myapp 8 | 9 | [14] Starting application 10 | [14] Got response 11 | [14] Service mysql.myapp is not created. Creating 12 | 13 | Pulling repository mysql{ 14 | u'error': u'Get https://index.docker.io/v1/repositories/library/mysql/images: dial tcp: lookup index.docker.io on 192.168.0.1:53: read udp 192.168.0.1:53: i/o timeout', 15 | u'errorDetail': { 16 | u'message': u'Get https://index.docker.io/v1/repositories/library/mysql/images: dial tcp: lookup index.docker.io on 192.168.0.1:53: read udp 192.168.0.1:53: i/o timeout', 17 | }, 18 | } 19 | 20 | : [>, >] 21 | 22 | 23 | $ docker pull mysql 24 | 25 | Pulling repository mysql 26 | FATA[0025] Get https://index.docker.io/v1/repositories/library/mysql/images: dial tcp: lookup index.docker.io on 192.168.0.1:53: read udp 192.168.0.1:53: i/o timeout 27 | 28 | https://github.com/kitematic/kitematic/issues/592 29 | 30 | docker-machine ssh dev 31 | echo "nameserver 8.8.8.8" > /etc/resolv.conf 32 | 33 | 34 | Ping google.com 35 | -------------------- 36 | 37 | Restart kitematic if there is no internet connection 38 | 39 | Development on macos 40 | =============================== 41 | 42 | docker run -d -v /opt/mcloud/local/lib/python2.7/site-packages/mcloud:/Users/alex/dev/mcloud/mcloud -v /Users:/Users --name mcloud mcloud/mcloud-osx 43 | 44 | https://cryptography.io/en/latest/installation/#using-your-own-openssl-on-os-x 45 | 46 | $ brew install openssl 47 | $ env ARCHFLAGS="-arch x86_64" LDFLAGS="-L/usr/local/opt/openssl/lib" CFLAGS="-I/usr/local/opt/openssl/include" pip install cryptography 48 | 49 | brew install pkg-config libffi 50 | PKG_CONFIG_PATH=/usr/local/opt/libffi/lib/pkgconfig pip install cffi 51 | 52 | Other dependencies are installed as they are. 53 | -------------------------------------------------------------------------------- /docs/source/use/publish.rst: -------------------------------------------------------------------------------- 1 | 2 | Application publishing 3 | =========================== 4 | 5 | Commands are about assigning the public URLs to the applications, which essentially is often the way how the newly deployed applications get "published" or "unpublished". 6 | 7 | 8 | Publish 9 | ----------- 10 | 11 | Assign URL to an application:: 12 | 13 | $ mcloud publish app my_domain.com [--ssl] 14 | 15 | --ssl means https://my_domain.com 16 | 17 | .. note:: 18 | You should publish both SSL and non-SSL version of URL if your application handles two protocols. 19 | 20 | 21 | Unpublish 22 | ----------- 23 | 24 | Remove an URL assignment from an application:: 25 | 26 | $ mcloud unpublish my_domain.com [--ssl] 27 | 28 | Application name is not needed. 29 | 30 | 31 | 32 | How it works 33 | ---------------- 34 | 35 | Publishing means exposing application under external domain name. 36 | 37 | First make sure domain name is pointing to the correct ip address by pinging it:: 38 | 39 | $ ping my-domain.com 40 | 41 | Then you can say mcloud to publish application:: 42 | 43 | $ mcloud -h publish flask-redis@ my-domain.com 44 | 45 | Then you will see your domain name in url list in *mcloud list* command. 46 | Load balancer is already reconfigured, so you can open your url in browser. 47 | 48 | If you need SSL for your domain, you can publish ssl version of domain as well:: 49 | 50 | $ mcloud -h publish flask-redis my-domain.com --ssl 51 | 52 | .. note:: 53 | Your application should be ready for ssl trafic. read :ref:`ssl` for details. 54 | 55 | Unpublish is similar:: 56 | 57 | $ mcloud -h unpublish my-domain.com 58 | 59 | or:: 60 | 61 | $ mcloud -h unpublish my-domain.com --ssl 62 | 63 | App name is not needed here, as MCloud nows to which application app domain belongs. 64 | One domain may belong to only one application. 65 | 66 | .. note:: 67 | "my-domain.com --ssl" is different domain than "my-domain.com" by MCLoud opinion, 68 | so you can bind SSL version and non-ssl to different applications. 69 | 70 | -------------------------------------------------------------------------------- /tests/test_events.py: -------------------------------------------------------------------------------- 1 | import inject 2 | from mcloud.events import EventBus 3 | import pytest 4 | from twisted.internet import reactor 5 | 6 | import txredisapi as redis 7 | 8 | @pytest.inlineCallbacks 9 | def test_events(): 10 | inject.clear() 11 | rc = yield redis.Connection(dbid=2) 12 | yield rc.flushdb() 13 | 14 | eb = EventBus(rc) 15 | yield eb.connect() 16 | 17 | test_events.test = None 18 | 19 | def boo(pattern, message): 20 | assert message == 'hoho' 21 | assert pattern == 'foo' 22 | test_events.test = message 23 | 24 | eb.on('foo', boo) 25 | 26 | yield eb.fire_event('foo', 'hoho') 27 | 28 | def check_results(): 29 | assert test_events.test == 'hoho' 30 | 31 | reactor.callLater(50, check_results) 32 | 33 | 34 | @pytest.inlineCallbacks 35 | def test_events_pattern(): 36 | inject.clear() 37 | rc = yield redis.Connection(dbid=2) 38 | yield rc.flushdb() 39 | 40 | eb = EventBus(rc) 41 | yield eb.connect() 42 | 43 | test_events_pattern.test = None 44 | 45 | def boo(pattern, message): 46 | assert message == 'hoho' 47 | assert pattern == 'foo.baz' 48 | test_events_pattern.test = message 49 | 50 | eb.on('foo.*', boo) 51 | 52 | yield eb.fire_event('foo.baz', 'hoho') 53 | 54 | def check_results(): 55 | assert test_events_pattern.test == 'hoho' 56 | 57 | reactor.callLater(50, check_results) 58 | 59 | 60 | @pytest.inlineCallbacks 61 | def test_events_pattern_wrong(): 62 | inject.clear() 63 | rc = yield redis.Connection(dbid=2) 64 | yield rc.flushdb() 65 | 66 | eb = EventBus(rc) 67 | yield eb.connect() 68 | 69 | test_events_pattern_wrong.test = None 70 | 71 | def boo(pattern, message): 72 | assert message == 'hoho' 73 | assert pattern == 'foo.baz' 74 | test_events_pattern_wrong.test = message 75 | 76 | eb.on('bar.*', boo) 77 | 78 | yield eb.fire_event('foo.baz', 'hoho') 79 | 80 | def check_results(): 81 | assert test_events_pattern_wrong.test is None 82 | 83 | reactor.callLater(50, check_results) 84 | 85 | -------------------------------------------------------------------------------- /mcloud/ssl.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from OpenSSL.crypto import PKey, X509 3 | import inject 4 | 5 | from twisted.internet import ssl, reactor 6 | from twisted.internet._sslverify import Certificate, KeyPair 7 | from twisted.internet.defer import inlineCallbacks 8 | import txredisapi 9 | 10 | 11 | class CtxFactory(ssl.ClientContextFactory): 12 | 13 | def __init__(self, key, crt): 14 | self.key = key 15 | self.crt = crt 16 | 17 | def getContext(self): 18 | from OpenSSL import SSL 19 | 20 | self.method = SSL.SSLv23_METHOD 21 | ctx = ssl.ClientContextFactory.getContext(self) 22 | 23 | if isinstance(self.crt, X509): 24 | ctx.use_certificate(self.crt) 25 | else: 26 | ctx.use_certificate_file(self.crt) 27 | 28 | if isinstance(self.key, PKey): 29 | ctx.use_privatekey(self.key) 30 | else: 31 | ctx.use_privatekey_file(self.key) 32 | 33 | return ctx 34 | 35 | 36 | def verifyCallback(connection, x509, errnum, errdepth, ok): 37 | 38 | if not ok: 39 | print 'invalid cert from subject:', x509.get_subject() 40 | return False 41 | else: 42 | print 'Subject is: %s' % x509.get_subject().commonName 43 | print "Certs are fine" 44 | 45 | # return False 46 | return True 47 | 48 | def listen_ssl(port, resource, interface): 49 | 50 | settings = inject.instance('settings') 51 | 52 | from OpenSSL import SSL 53 | 54 | from twisted.internet import ssl 55 | 56 | myContextFactory = ssl.DefaultOpenSSLContextFactory( 57 | settings.ssl.key, settings.ssl.cert, sslmethod=SSL.SSLv23_METHOD 58 | ) 59 | ctx = myContextFactory.getContext() 60 | 61 | ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_CLIENT_ONCE | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verifyCallback) 62 | ctx.set_session_cache_mode(SSL.SESS_CACHE_BOTH) 63 | 64 | # Since we have self-signed certs we have to explicitly 65 | # tell the server to trust them. 66 | 67 | ctx.load_verify_locations(settings.ssl.ca) 68 | ctx.set_session_id(str(datetime.datetime.now())) 69 | 70 | reactor.listenSSL(port, resource, myContextFactory, interface=interface) 71 | -------------------------------------------------------------------------------- /tests/test_rpc_client.py: -------------------------------------------------------------------------------- 1 | from mcloud.rpc_client import ApiRpcClient 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def client(): 7 | return ApiRpcClient() 8 | 9 | 10 | def test_ref_expression_app(client): 11 | result = client.parse_app_ref('foo.boo', {}) 12 | 13 | assert result == ('boo', None) 14 | 15 | 16 | def test_ref_expression_app(client): 17 | result = client.parse_app_ref('foo.boo', {}) 18 | 19 | assert result == ('boo', 'foo') 20 | 21 | def test_ref_app_in_args(client): 22 | result = client.parse_app_ref('boo.', {'app': 'foo'}) 23 | 24 | assert result == ('foo', 'boo') 25 | 26 | 27 | # def test_ref_empty(client): 28 | # result = client.parse_app_ref('', {}) 29 | # 30 | # assert result == ('mcloud', None) # mcloud is current dir 31 | 32 | def test_ref_require_app(client): 33 | 34 | with pytest.raises(ValueError): 35 | client.parse_app_ref('a.b.c.d.e.f', {}, require_app=True) 36 | 37 | def test_ref_app_only_with_service(client): 38 | 39 | with pytest.raises(ValueError): 40 | client.parse_app_ref('foo.boo', {}, app_only=True) 41 | 42 | def test_ref_requeire_service_without_service(client): 43 | 44 | with pytest.raises(ValueError): 45 | client.parse_app_ref('boo', {}, require_service=True) 46 | 47 | 48 | def test_ref_need_host(client): 49 | 50 | client.host = 'my.host' 51 | result = client.parse_app_ref('foo.boo', {}, require_host=True) 52 | 53 | assert result == ('my.host', 'boo', 'foo') 54 | 55 | 56 | def test_ref_need_host_specified_hostname(client): 57 | 58 | client.host = 'my.host' 59 | result = client.parse_app_ref('foo.boo@some-host.at.some.location.ua', {}, require_host=True) 60 | 61 | assert result == ('some-host.at.some.location.ua', 'boo', 'foo') 62 | 63 | 64 | 65 | def test_ref_need_host_specified_ip(client): 66 | 67 | client.host = 'my.host' 68 | result = client.parse_app_ref('foo.boo@192.168.0.32', {}, require_host=True) 69 | 70 | assert result == ('192.168.0.32', 'boo', 'foo') 71 | 72 | 73 | def test_with_on_host(client): 74 | 75 | client.host = 'foo' 76 | 77 | assert client.host == 'foo' 78 | 79 | with client.override_host('boo'): 80 | assert client.host == 'boo' 81 | 82 | assert client.host == 'foo' -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/README.md: -------------------------------------------------------------------------------- 1 | # term.js 2 | 3 | A full xterm clone written in javascript. Used by 4 | [**tty.js**](https://github.com/chjj/tty.js). 5 | 6 | ## Example 7 | 8 | Server: 9 | 10 | ``` js 11 | var term = require('term.js'); 12 | app.use(term.middleware()); 13 | ... 14 | ``` 15 | 16 | Client: 17 | 18 | ``` js 19 | window.addEventListener('load', function() { 20 | var socket = io.connect(); 21 | socket.on('connect', function() { 22 | var term = new Terminal({ 23 | cols: 80, 24 | rows: 24, 25 | screenKeys: true 26 | }); 27 | 28 | term.on('data', function(data) { 29 | socket.emit('data', data); 30 | }); 31 | 32 | term.on('title', function(title) { 33 | document.title = title; 34 | }); 35 | 36 | term.open(document.body); 37 | 38 | term.write('\x1b[31mWelcome to term.js!\x1b[m\r\n'); 39 | 40 | socket.on('data', function(data) { 41 | term.write(data); 42 | }); 43 | 44 | socket.on('disconnect', function() { 45 | term.destroy(); 46 | }); 47 | }); 48 | }, false); 49 | ``` 50 | 51 | ## Tmux-like 52 | 53 | While term.js has always supported copy/paste using the mouse, it now also 54 | supports several keyboard based solutions for copy/paste. 55 | 56 | term.js includes a tmux-like selection mode (enabled with the `screenKeys` 57 | option) which makes copy and paste very simple. `Ctrl-A` enters `prefix` mode, 58 | from here you can type `Ctrl-V` to paste. Press `[` in prefix mode to enter 59 | selection mode. To select text press `v` (or `space`) to enter visual mode, use 60 | `hjkl` to navigate and create a selection, and press `Ctrl-C` to copy. 61 | 62 | `Ctrl-C` (in visual mode) and `Ctrl-V` (in prefix mode) should work in any OS 63 | for copy and paste. `y` (in visual mode) will work for copying only on X11 64 | systems. It will copy to the primary selection. 65 | 66 | Note: `Ctrl-C` will also work in prefix mode for the regular OS/browser 67 | selection. If you want to select text with your mouse and copy it to the 68 | clipboard, simply select the text and type `Ctrl-A + Ctrl-C`, and 69 | `Ctrl-A + Ctrl-V` to paste it. 70 | 71 | For mac users: consider `Ctrl` to be `Command/Apple` above. 72 | 73 | ## License 74 | 75 | Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 76 | -------------------------------------------------------------------------------- /mcloud/static_old/components/angular/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular 15 | ``` 16 | 17 | Then add a ` 21 | ``` 22 | 23 | Note that this package is not in CommonJS format, so doing `require('angular')` will return `undefined`. 24 | If you're using [Browserify](https://github.com/substack/node-browserify), you can use 25 | [exposify](https://github.com/thlorenz/exposify) to have `require('angular')` return the `angular` 26 | global. 27 | 28 | ### bower 29 | 30 | ```shell 31 | bower install angular 32 | ``` 33 | 34 | Then add a ` 38 | ``` 39 | 40 | ## Documentation 41 | 42 | Documentation is available on the 43 | [AngularJS docs site](http://docs.angularjs.org/). 44 | 45 | ## License 46 | 47 | The MIT License 48 | 49 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 50 | 51 | Permission is hereby granted, free of charge, to any person obtaining a copy 52 | of this software and associated documentation files (the "Software"), to deal 53 | in the Software without restriction, including without limitation the rights 54 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 55 | copies of the Software, and to permit persons to whom the Software is 56 | furnished to do so, subject to the following conditions: 57 | 58 | The above copyright notice and this permission notice shall be included in 59 | all copies or substantial portions of the Software. 60 | 61 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 62 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 63 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 64 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 65 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 66 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 67 | THE SOFTWARE. 68 | -------------------------------------------------------------------------------- /docs/source/guide/4_production.rst: -------------------------------------------------------------------------------- 1 | 2 | ================================= 3 | Production mcloud deployment 4 | ================================= 5 | 6 | 7 | Install production Mcloud Server 8 | ----------------------------------- 9 | 10 | Installation of production server is even easier than local deployment istallation. 11 | 12 | Command for production server you need linux os with docker installed. Some 13 | cloud providers already have images with such config. 14 | 15 | 16 | Spin up the container:: 17 | 18 | $ docker run -d --restart always -v /etc/mcloud:/etc/mcloud -v /root:/root -v /var/run/docker.sock:/var/run/docker.sock -p 7080:7080 --name mcloud mcloud/mcloud 19 | 20 | Haprxoy is also needed:: 21 | 22 | $ docker exec -it mcloud mcloud-plugins install mcloud-plugin-haproxy 23 | 24 | Major diference from local installation is that we mount /root directory instead of home, /etc/mcloud directoy and we also expose 7080 port. 25 | 26 | /root directory stores all application files, so it's always good idea to have direct access to this. 27 | /etc/mcloud is place where we can create mcloud-server.yml config. 28 | 29 | .. note:: 30 | 31 | Protecting Mcloud with SSL 32 | -------------------------------- 33 | 34 | 35 | As an authentication method, mcloud use SSL. 36 | For both client and server you will need to create and sign certificates. 37 | 38 | Easies way to do this is to use XCA tool: http://sourceforge.net/projects/xca/ 39 | 40 | Remote machine configuration 41 | ******************************* 42 | 43 | Create /etc/mcloud/mcloud-server.yml:: 44 | 45 | ssl: 46 | enabled: true 47 | 48 | On your remote machine. 49 | 50 | And put three files into /etc/mcloud: 51 | 52 | - ca.crt 53 | - server.crt 54 | - server.key 55 | 56 | Put correct privileges to files:: 57 | 58 | sudo chmod go-rwx -R /etc/mcloud 59 | 60 | Restart mcloud server:: 61 | 62 | docker restart mcloud 63 | 64 | Local machine 65 | ************************ 66 | 67 | 68 | Put certificates files into ~/.mcloud/: 69 | 70 | - my_server.crt 71 | - my_server.key 72 | 73 | Instead of "my_server" put your hostname you use to connect to mcloud. 74 | 75 | Now `mcloud` command will autodetect and use your certificates. 76 | 77 | 78 | Testing connection to server 79 | -------------------------------------------- 80 | 81 | :: 82 | 83 | $ mcloud --host myserver_hostname_or_ip list 84 | 85 | Command should show empty application list. 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/source/use/zero_downtime.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ======================================= 4 | "Zero" down-time updates with publish 5 | ======================================= 6 | 7 | Simple version of zero down-time scenario may be achived by deploying 8 | two versions of applications, and publishing only one in time. 9 | 10 | .. note:: 11 | This is not really zero-downtime, "it's minimal possible downtime" 12 | 13 | Deploy initial version 14 | ======================== 15 | 16 | Create two tenants:: 17 | 18 | $ mcloud -h init tenantA_flask-redis mcloud.yml 19 | $ mcloud -h init tenantB_flask-redis mcloud.yml 20 | 21 | ... do usual deploy here 22 | 23 | Now publish tenant A:: 24 | 25 | $ mcloud -h publish tenantA_flask-redis my-domain.com 26 | 27 | 28 | Deploy & test 29 | ======================== 30 | 31 | Deploy new code & structure to tenant B 32 | 33 | $ mcloud sync . tenantB_flask-redis@ 34 | $ mcloud -h restart tenantB_flask-redis 35 | $ mcloud -h config --update tenantB_flask-redis 36 | 37 | Copy data from live server:: 38 | 39 | $ mcloud sync tenantA_flask-redis@ tenantB_flask-redis@ 40 | 41 | Now publish tenant B, so testers can test through the changes:: 42 | 43 | $ mcloud -h publish tenantB_flask-redis testing.my-domain.com 44 | 45 | 46 | Publish changes 47 | ======================== 48 | 49 | This steps should be done quickly, then for you customers it will look like as 50 | several seconds hang-up:: 51 | 52 | $ mcloud -h pause tenantA_flask-redis 53 | $ mcloud sync --force tenantA_flask-redis@ tenantB_flask-redis@ 54 | $ mcloud -h publish tenantB_flask-redis my-domain.com 55 | 56 | Rollback 57 | ======================== 58 | 59 | If happens you still have some terrible bug on tenant B, that was not discovered during testing session, 60 | you can rollback easily:: 61 | 62 | $ mcloud -h publish tenantA_flask-redis my-domain.com 63 | $ mcloud -h resume tenantA_flask-redis 64 | 65 | On success 66 | ======================== 67 | 68 | If update went well, you can resume tenant A, so next time this server will get updated:: 69 | 70 | $ mcloud -h resume tenantA_flask-redis 71 | 72 | 73 | Next update 74 | ======================== 75 | 76 | On next update scenario is same, except you will deploy new version to tenant A, and then swap with 77 | tenant B, that is in production. -------------------------------------------------------------------------------- /mcloud/static_old/components/term.js/example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * term.js 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | */ 5 | 6 | var http = require('http') 7 | , express = require('express') 8 | , io = require('socket.io') 9 | , pty = require('pty.js') 10 | , terminal = require('../'); 11 | 12 | /** 13 | * term.js 14 | */ 15 | 16 | process.title = 'term.js'; 17 | 18 | /** 19 | * Dump 20 | */ 21 | 22 | var stream; 23 | if (process.argv[2] === '--dump') { 24 | stream = require('fs').createWriteStream(__dirname + '/dump.log'); 25 | } 26 | 27 | /** 28 | * Open Terminal 29 | */ 30 | 31 | var buff = [] 32 | , socket 33 | , term; 34 | 35 | term = pty.fork(process.env.SHELL || 'sh', [], { 36 | name: require('fs').existsSync('/usr/share/terminfo/x/xterm-256color') 37 | ? 'xterm-256color' 38 | : 'xterm', 39 | cols: 80, 40 | rows: 24, 41 | cwd: process.env.HOME 42 | }); 43 | 44 | term.on('data', function(data) { 45 | if (stream) stream.write('OUT: ' + data + '\n-\n'); 46 | return !socket 47 | ? buff.push(data) 48 | : socket.emit('data', data); 49 | }); 50 | 51 | console.log('' 52 | + 'Created shell with pty master/slave' 53 | + ' pair (master: %d, pid: %d)', 54 | term.fd, term.pid); 55 | 56 | /** 57 | * App & Server 58 | */ 59 | 60 | var app = express() 61 | , server = http.createServer(app); 62 | 63 | app.use(function(req, res, next) { 64 | var setHeader = res.setHeader; 65 | res.setHeader = function(name) { 66 | switch (name) { 67 | case 'Cache-Control': 68 | case 'Last-Modified': 69 | case 'ETag': 70 | return; 71 | } 72 | return setHeader.apply(res, arguments); 73 | }; 74 | next(); 75 | }); 76 | 77 | app.use(express.basicAuth(function(user, pass, next) { 78 | if (user !== 'foo' || pass !== 'bar') { 79 | return next(true); 80 | } 81 | return next(null, user); 82 | })); 83 | 84 | app.use(express.static(__dirname)); 85 | app.use(terminal.middleware()); 86 | 87 | server.listen(8080); 88 | 89 | server.on('connection', function(socket) { 90 | var address = socket.remoteAddress; 91 | if (address !== '127.0.0.1' && address !== '::1') { 92 | try { 93 | socket.destroy(); 94 | } catch (e) { 95 | ; 96 | } 97 | console.log('Attempted connection from %s. Refused.', address); 98 | } 99 | }); 100 | 101 | /** 102 | * Sockets 103 | */ 104 | 105 | io = io.listen(server, { 106 | log: false 107 | }); 108 | 109 | io.sockets.on('connection', function(sock) { 110 | socket = sock; 111 | 112 | socket.on('data', function(data) { 113 | if (stream) stream.write('IN: ' + data + '\n-\n'); 114 | term.write(data); 115 | }); 116 | 117 | socket.on('disconnect', function() { 118 | socket = null; 119 | }); 120 | 121 | while (buff.length) { 122 | socket.emit('data', buff.shift()); 123 | } 124 | }); 125 | -------------------------------------------------------------------------------- /tests/test_image_builders.py: -------------------------------------------------------------------------------- 1 | from flexmock import flexmock 2 | from mcloud.service import Service 3 | 4 | import os 5 | from mcloud.container import PrebuiltImageBuilder, DockerfileImageBuilder 6 | from mcloud.test_utils import mock_docker 7 | import pytest 8 | from twisted.internet import defer 9 | 10 | 11 | @pytest.inlineCallbacks 12 | def test_image_builder_prebuilt(): 13 | 14 | with mock_docker() as docker_mock: 15 | dm = docker_mock 16 | """@type : flexmock.Mock""" 17 | 18 | #dm.should_receive('images').with_args(name='foo/bar').and_return([]) 19 | 20 | dm.should_receive('images').with_args(name='foo/bar').once().and_return(defer.succeed([])) 21 | dm.should_receive('pull').with_args(name='foo/bar', ticket_id=123123, tag=None).once().and_return(defer.succeed(['foo', 'bar', 'baz'])) 22 | 23 | s = Service(client=dm) 24 | 25 | builder = PrebuiltImageBuilder('foo/bar') 26 | 27 | result = yield builder.build_image(ticket_id=123123, service=s) 28 | assert result == 'foo/bar' 29 | 30 | @pytest.inlineCallbacks 31 | def test_image_builder_prebuilt_already_built(): 32 | 33 | with mock_docker() as docker_mock: 34 | dm = docker_mock 35 | """@type : flexmock.Mock""" 36 | 37 | #dm.should_receive('images').with_args(name='foo/bar').and_return([]) 38 | 39 | dm.should_receive('pull').never() 40 | dm.should_receive('images').with_args(name='foo/bar').once().and_return(defer.succeed([ 41 | { 42 | "RepoTags": [ 43 | "foo:bar", 44 | ], 45 | "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", 46 | "Created": 1365714795, 47 | "Size": 131506275, 48 | "VirtualSize": 131506275 49 | } 50 | ])) 51 | 52 | s = Service(client=dm) 53 | 54 | builder = PrebuiltImageBuilder('foo/bar') 55 | 56 | result = yield builder.build_image(ticket_id=123123, service=s) 57 | assert result == 'foo/bar' 58 | 59 | 60 | @pytest.inlineCallbacks 61 | def test_image_builder_create_archive(): 62 | 63 | builder = DockerfileImageBuilder(os.path.join(os.path.dirname(__file__), '_files/ct_bash')) 64 | 65 | file = yield builder.create_archive() 66 | 67 | assert len(file) > 30 68 | 69 | 70 | @pytest.inlineCallbacks 71 | def test_image_builder_build(): 72 | 73 | with mock_docker() as client: 74 | 75 | builder = DockerfileImageBuilder(os.path.join(os.path.dirname(__file__), '_files/ct_bash')) 76 | 77 | flexmock(builder) 78 | 79 | from mcloud.service import Service 80 | s = Service(client=client) 81 | 82 | builder.should_receive('create_archive').once().and_return(defer.succeed('foo')) 83 | 84 | client.should_receive('build_image').with_args('foo', ticket_id=123123).and_return(defer.succeed('baz')) 85 | 86 | result = yield builder.build_image(ticket_id=123123, service=s) 87 | 88 | assert result == 'baz' 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/mcloudlogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 11 | 15 | 16 | 17 | 22 | 24 | 28 | 32 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /mcloud/static/client/src/app/getting-started/getting-started.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Note that this is only a getting started guide, for more detailed information about the build system, the available tasks, the configuration of the build or anything else, please refer to the documentation on the GitHub project. 5 |

6 | 7 |

What and Why

8 |

9 | angular-kickstart is an opinionated kickstart for single page application development with AngularJS. It makes your development and testing easy, keeps the structure of the project consistent and allows you to create a fully optimized 10 | production release withe ease. After having developed a lot of AngularJS projects I decided to collect here what I've learnt. 11 |

12 | 13 |

Getting started

14 |

15 | Install 16 | node.js. Then 17 | sass, gulp and bower if you haven't yet. 18 |

19 | 20 |
21 |     
22 |     $ gem install sass
23 |     $ npm -g install gulp bower
24 |     
25 | 
26 | 27 |

28 | After that, install 29 | angular-kickstart - download the latest release (or clone the master branch if want to run the development version). Unzip the project and cd into it, then 30 | install bower and npm dependencies, and run the application in development mode. 31 |

32 | 33 |
34 |     
35 |     $ npm install
36 |     $ bower install
37 |     $ gulp serve
38 |     
39 | 
40 | 41 |

42 | You are now ready to go, your applcation is available at http://127.0.0.1:3000. 43 |

44 |

45 | You are now ready to start coding, every file you add, edit or delete into the 46 | /client folder, will be handled by the build system and the browser will reload. 47 |

48 |

49 | When you are ready to build a production release there is a task for that. 50 |

51 | 52 |
53 |     
54 |     $ gulp serve:dist
55 |     
56 | 
57 | 58 |

59 | This task will lint your code, optimize css js and images files, run unit tests. After the task has successfully finished, you can find an optimized version of your project in the 60 | /build/dist folder. 61 |

62 | 63 |

64 | Other tasks are available: 65 |

66 | 67 |
68 |     
69 |     #for developing running unit test on every file change.
70 |     $ gulp serve:tdd
71 | 
72 |     #for running e2e test. (you application should be running on http://127.0.0.1:3000)
73 |     $ gulp test:e2e
74 | 
75 |     #for running unit tests one time then exit.
76 |     $ gulp test:unit
77 |     
78 | 
79 |

80 | 81 | Full documentation on GitHub 82 | 83 |

84 | 85 |
86 |
87 | 88 | -------------------------------------------------------------------------------- /mcloud/static_old/assets/images/mcloudlogomono.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 11 | 15 | 16 | 17 | 22 | 24 | 28 | 32 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /release.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Implement following release procedure: 4 | 5 | - Take name of current branch 0.X 6 | - Create a new tag with name 0.X.Y (by incrementing last digit) 7 | - dump CHANGELOG.txt file 8 | - update debian changelog 9 | - build and upload package to pypi 10 | - build debian package and add id to debian repo 11 | 12 | """ 13 | 14 | import argparse 15 | import datetime 16 | import pipes 17 | from time import strftime 18 | import os 19 | import re 20 | from git import Repo 21 | import sys 22 | 23 | 24 | def match_versioning_branch_format(name): 25 | if not hasattr(match_versioning_branch_format, 're'): 26 | match_versioning_branch_format.re = re.compile('^\d+\.\d+$') 27 | return match_versioning_branch_format.re.match(name) 28 | 29 | 30 | def find_latest_tag(repo, branch_name): 31 | 32 | preg = re.compile('^%s\.(\d+)$' % re.escape(branch_name)) 33 | 34 | tags_found = {} 35 | for ref in repo.tags: 36 | result = preg.match(ref.tag.tag) 37 | if result: 38 | tags_found[int(result.group(1))] = ref 39 | 40 | if len(tags_found) == 0: 41 | return 0, None 42 | 43 | max_key = max(tags_found.keys()) 44 | 45 | return max_key, tags_found[max_key] 46 | 47 | 48 | def file_prepend(filename, data): 49 | f = open(filename, 'r') 50 | temp = f.read() 51 | f.close() 52 | f = open(filename, 'w') 53 | f.write(data) 54 | f.write(temp) 55 | f.close() 56 | 57 | 58 | if __name__ == "__main__": 59 | 60 | repo = Repo(".") 61 | #repo.remotes.origin.fetch() 62 | 63 | branch_name = repo.active_branch.name 64 | if not match_versioning_branch_format(branch_name): 65 | sys.stderr.write("Current branch doesn't seem to be correct version format '%%d.%%d' : %s \n" % branch_name) 66 | exit(1) 67 | 68 | max_key, ref = find_latest_tag(repo, branch_name) 69 | 70 | if not ref or ref.commit.hexsha != repo.head.commit.hexsha: 71 | new_tag_name = '%s.%d' % (branch_name, max_key + 1) 72 | # new_ref = repo.create_tag(new_tag_name, message='New version: %s' % new_tag_name) 73 | 74 | 75 | logs = [] 76 | 77 | if ref: 78 | from debian import changelog 79 | 80 | with open('debian/changelog') as f: 81 | dch = changelog.Changelog(f) 82 | dch.new_block() 83 | dch.set_author('Alex Rudakov ') 84 | dch.set_date(strftime('%a, %d %b %Y %H:%M:%S %z')) 85 | dch.set_distributions('trusty') 86 | dch.set_package('mcloud') 87 | dch.set_urgency('medium') 88 | dch.set_version('%s-1' % new_tag_name) 89 | 90 | dch.add_change('') 91 | 92 | for commit in repo.iter_commits('%s..%s' % (ref.tag.tag, new_tag_name)): 93 | message = commit.message.strip() 94 | dch.add_change(' * %s' % re.sub('\s+', ' ', message)) 95 | logs.append(message) 96 | 97 | dch.add_change('') 98 | 99 | with open('debian/changelog', 'w+') as f: 100 | dch.write_to_open_file(f) 101 | 102 | data = '%s %s\n *%s\n\n' % (new_tag_name, datetime.datetime.now().isoformat(), '\n\t *'.join(logs)) 103 | 104 | file_prepend('CHANGES.txt', data) 105 | 106 | sys.stdout.write(new_tag_name) 107 | 108 | 109 | else: 110 | 111 | sys.stdout.write('0') 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /mcloud/container.py: -------------------------------------------------------------------------------- 1 | from StringIO import StringIO 2 | import logging 3 | import tarfile 4 | from tempfile import mkdtemp 5 | from abc import abstractmethod 6 | from mcloud.util import Interface 7 | from twisted.internet import reactor, defer 8 | 9 | logger = logging.getLogger('mcloud.application') 10 | from twisted.python import log 11 | 12 | 13 | 14 | class IContainerBuilder(Interface): 15 | pass 16 | 17 | 18 | class IImageBuilder(Interface): 19 | 20 | @abstractmethod 21 | def build_image(self, ticket_id, service): 22 | pass 23 | 24 | 25 | class PrebuiltImageBuilder(IImageBuilder): 26 | def __init__(self, image): 27 | super(PrebuiltImageBuilder, self).__init__() 28 | 29 | self.image = image 30 | 31 | @defer.inlineCallbacks 32 | def build_image(self, ticket_id, service): 33 | 34 | log.msg('[%s] Building image "%s".', ticket_id, self.image) 35 | 36 | name = self.image 37 | tag = None 38 | if ':' in name: 39 | name, tag = name.split(':') 40 | 41 | images = yield service.client.images(name=name) 42 | 43 | if tag: 44 | images = [x for x in images if self.image in x['RepoTags']] 45 | 46 | if not images: 47 | log.msg('[%s] Image is not there. Pulling "%s" ...', ticket_id, self.image) 48 | 49 | yield service.client.pull(name, ticket_id, tag) 50 | 51 | log.msg('[%s] Image "%s" is ready to use.', ticket_id, self.image) 52 | defer.returnValue(self.image) 53 | 54 | 55 | class CanNotAccessPath(Exception): 56 | pass 57 | 58 | 59 | class DockerfileImageBuilder(IImageBuilder): 60 | def __init__(self, path): 61 | super(DockerfileImageBuilder, self).__init__() 62 | self.path = path 63 | 64 | self.image_id = None 65 | 66 | def create_archive(self): 67 | d = defer.Deferred() 68 | 69 | def archive(): 70 | memfile = StringIO() 71 | try: 72 | t = tarfile.open(mode='w', fileobj=memfile) 73 | t.add(self.path, arcname='.') 74 | d.callback(memfile.getvalue()) 75 | except OSError as e: 76 | d.errback(CanNotAccessPath('Can not access %s: %s' % (self.path, str(e)))) 77 | except Exception as e: 78 | d.errback(e) 79 | finally: 80 | memfile.close() 81 | 82 | reactor.callLater(0, archive) 83 | return d 84 | 85 | @defer.inlineCallbacks 86 | def build_image(self, ticket_id, service): 87 | archive = yield self.create_archive() 88 | ret = yield service.client.build_image(archive, ticket_id=ticket_id) 89 | defer.returnValue(ret) 90 | 91 | 92 | class VirtualFolderImageBuilder(DockerfileImageBuilder): 93 | 94 | def __init__(self, files): 95 | self.files = files 96 | 97 | super(VirtualFolderImageBuilder, self).__init__(None) 98 | 99 | self.image_id = None 100 | 101 | def build_image(self, ticket_id, service): 102 | 103 | tdir = mkdtemp() 104 | for file_, source in self.files.items(): 105 | with open(tdir + '/%s' % file_, 'w+') as f: 106 | f.write(source) 107 | 108 | self.path = tdir 109 | 110 | return super(VirtualFolderImageBuilder, self).build_image(ticket_id, service) 111 | 112 | 113 | class InlineDockerfileImageBuilder(VirtualFolderImageBuilder): 114 | def __init__(self, source): 115 | super(InlineDockerfileImageBuilder, self).__init__({ 116 | 'Dockerfile': source 117 | }) -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.8.3 2014-11-18T11:59:31.626507 2 | *Bugfixes and improvements 3 | *Remove broken submodule 4 | *update -> config 5 | *STarted implementing push/pull 6 | *Merge branch 'master' of github.com:modera/mcloud 7 | *Fix broken sync for subvolumes 8 | *MCloud 0.8.2 9 | 10 | 0.8.2 2014-11-14T17:10:18.689901 11 | *Merge branch 'master' into 0.8 12 | *CLOUD-9 ~xxx syntax in configs 13 | *New version deployed 14 | 15 | 0.8.1 2014-11-14T09:24:40.263009 16 | *Fixed tests 17 | *CLOUD-60: fix problem with version updates on ubuntu and @me command. 18 | *Rsync based sync + --watch 19 | *Merge branch 'master' of github.com:modera/mcloud 20 | *Cleanup docker container & images 21 | *Refining docs, clearing typos. Updated the docs layout page and styles. 22 | 23 | 0.7.11 2014-11-06T13:17:44.158563 24 | *Merge branch 'master' of github.com:modera/mcloud 25 | *Cleanup docker container & images 26 | *Refining docs, clearing typos. Updated the docs layout page and styles. 27 | *Removed extra deps from docs generator 28 | *Merge 29 | *Try to fix broken tests 30 | *Fixing stupid Sphinx format quirks. 31 | *Fixing docs formatting problems. 32 | *Getting started docs improvements. 33 | *Tests fixes 34 | *[CLOUD-17] Fix error on 'bad path' in 'build:', better error output 35 | *[CLOUD-21] Name checking for application + snapshot errors, in better way 36 | *Merge branch 'master' of github.com:modera/mcloud 37 | *Minor changes in docs 38 | *[docs] mcloud-rpc-server -> mcloud-server 39 | *Docs update 40 | 41 | 0.7.10 2014-11-06T13:15:45.564089 42 | *New version 43 | 44 | 0.7.9 2014-10-23T10:04:00.580961 45 | *New version 46 | 47 | 0.7.8 2014-10-23T10:02:12.953795 48 | *Bugfix: can not mount single file + covered bug with tests 49 | *Bugfix: can not mount '.' because of security filter 50 | 51 | 0.7.8 2014-10-23T09:47:23.248143 52 | *New version 53 | 54 | 0.7.7 2014-10-22T15:09:50.630454 55 | *Small bug fix: can not fetch image without tag 56 | 57 | 0.7.6 2014-10-22T12:36:01.648917 58 | *Fix deb versions back 59 | 60 | 0.7.5 2014-10-22T12:30:03.984158 61 | *Fix debian package to older version numbers 62 | 63 | 0.7.5 2014-10-22T12:27:29.973972 64 | *Fix debian package to older version numbers 65 | 66 | 0.7.4 2014-10-22T12:01:29.258519 67 | *Merge branch 'master' into 0.7 68 | *Fix life-cycle commands syntax a bit 69 | *small docs fixes 70 | *small docs fixes 71 | *small docs fixes 72 | 73 | 0.7.3 2014-10-22T12:00:33.960106 74 | *Fix 2.6 compatibility 75 | 76 | 0.7.2 2014-10-22T11:56:00.603694 77 | *New version, small docs fix 78 | 79 | 0.6.5 2014-10-15T15:02:59.729153 80 | *Improve ssl support 81 | 82 | 0.6.4 2014-10-15T14:07:49.815276 83 | *[CLOUD-36] Fix mcloud ignores _ 84 | 85 | 0.6.2 2014-10-14T15:01:53.198378 86 | 87 | * [CLOUD-30] Bugfixes in new ignore logic 88 | * [CLOUD-30] Improve ignorefile logic 89 | * [CLOUD-26] Fx unicode troubles in Run command 90 | * Updated ref to Vagrantfile in docs 91 | * Added Vagantfile 92 | * Fix problem with version.txt 93 | * Fix wrong title in docs 94 | * Fix path to version.txt 95 | * Better behavior of release.py 96 | 97 | 0.6.1 2014-10-11T11:00:33.832494 98 | 99 | * Docs theme update 100 | * Rename mfcloud -> mcloud 101 | * vm docs 102 | * Update docs 103 | * docs 104 | * mfcloud-full package added 105 | * Added debian tempfiles to ignore 106 | * Docs updated 107 | * Preparing debian package 108 | * Bugfix in run command 109 | -------------------------------------------------------------------------------- /mcloud/util.py: -------------------------------------------------------------------------------- 1 | from Queue import Queue, Empty 2 | from contextlib import contextmanager 3 | from abc import ABCMeta 4 | import inject 5 | import os 6 | from twisted.internet import reactor 7 | from twisted.internet.defer import Deferred 8 | from twisted.python.failure import Failure 9 | 10 | 11 | class Interface(object): 12 | __metaclass__ = ABCMeta 13 | 14 | @contextmanager 15 | def inject_services(configurator): 16 | inject.clear_and_configure(configurator) 17 | yield 18 | inject.clear() 19 | 20 | @contextmanager 21 | def injector(bind_config): 22 | 23 | def configurator(binder): 24 | for key, val in bind_config.items(): 25 | binder.bind(key, val) 26 | 27 | inject.clear_and_configure(configurator) 28 | yield 29 | inject.clear() 30 | 31 | class TxTimeoutEception(Exception): 32 | pass 33 | 34 | def txtimeout(deferred, timeout, fail): 35 | 36 | def _raise(): 37 | deferred.errback(Failure(TxTimeoutEception(fail))) 38 | 39 | delayedCall = reactor.callLater(timeout, _raise) 40 | 41 | def gotResult(result): 42 | if delayedCall.active(): 43 | delayedCall.cancel() 44 | return result 45 | deferred.addBoth(gotResult) 46 | 47 | return deferred 48 | 49 | class ValidationError(Exception): 50 | pass 51 | 52 | class UserError(Exception): 53 | pass 54 | 55 | 56 | import sys 57 | 58 | @contextmanager 59 | def safe_chdir(dir): 60 | before = os.getcwd() 61 | os.chdir(dir) 62 | yield 63 | os.chdir(before) 64 | 65 | def query_yes_no(question, default="yes"): 66 | """Ask a yes/no question via raw_input() and return their answer. 67 | 68 | "question" is a string that is presented to the user. 69 | "default" is the presumed answer if the user just hits . 70 | It must be "yes" (the default), "no" or None (meaning 71 | an answer is required of the user). 72 | 73 | The "answer" return value is one of "yes" or "no". 74 | """ 75 | valid = {"yes": True, "y": True, "ye": True, 76 | "no": False, "n": False} 77 | if default is None: 78 | prompt = " [y/n] " 79 | elif default == "yes": 80 | prompt = " [Y/n] " 81 | elif default == "no": 82 | prompt = " [y/N] " 83 | else: 84 | raise ValueError("invalid default answer: '%s'" % default) 85 | 86 | while True: 87 | sys.stdout.write(question + prompt) 88 | choice = raw_input().lower() 89 | if default is not None and choice == '': 90 | return valid[default] 91 | elif choice in valid: 92 | return valid[choice] 93 | else: 94 | sys.stdout.write("Please respond with 'yes' or 'no' " 95 | "(or 'y' or 'n').\n") 96 | 97 | 98 | from twisted.internet import protocol 99 | from twisted.internet import reactor 100 | 101 | 102 | class LessLongTextProtocol(protocol.ProcessProtocol): 103 | def __init__(self, long_text, on_exit): 104 | self.long_text = long_text 105 | self.on_exit = on_exit 106 | 107 | def connectionMade(self): 108 | print "Connected" 109 | # self.transport.write(self.long_text.encode('utf-8')) 110 | self.transport.write('kuku') 111 | self.transport.closeStdin() 112 | 113 | 114 | 115 | def errReceived(self, data): 116 | protocol.ProcessProtocol.errReceived(self, data) 117 | print(data) 118 | 119 | def outReceived(self, data): 120 | protocol.ProcessProtocol.outReceived(self, data) 121 | 122 | print data 123 | 124 | 125 | def processEnded(self, reason): 126 | self.on_exit.callback(True) 127 | -------------------------------------------------------------------------------- /tests/test_application.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from _pytest.runner import Exit 3 | from flexmock import flexmock 4 | from mcloud.application import Application, ApplicationController, AppDoesNotExist 5 | from mcloud.config import YamlConfig 6 | from mcloud.container import DockerfileImageBuilder, PrebuiltImageBuilder 7 | from mcloud.service import Service 8 | from mcloud.test_utils import real_docker 9 | from mcloud.txdocker import IDockerClient, DockerTwistedClient 10 | from mcloud.util import inject_services, txtimeout 11 | import os 12 | import pytest 13 | from twisted.internet import reactor, defer 14 | import txredisapi 15 | 16 | 17 | def test_new_app_instance(): 18 | 19 | app = Application({'path': 'foo/bar'}, name='foo') 20 | assert app.config['path'] == 'foo/bar' 21 | assert app.name == 'foo' 22 | 23 | @pytest.inlineCallbacks 24 | def test_app_load(): 25 | 26 | 27 | def timeout(): 28 | print('Can not connect to redis!') 29 | reactor.stop() 30 | 31 | redis = yield txtimeout(txredisapi.Connection(dbid=2), 2, timeout) 32 | yield redis.flushdb() 33 | 34 | 35 | def configure(binder): 36 | binder.bind(txredisapi.Connection, redis) 37 | binder.bind(IDockerClient, DockerTwistedClient()) 38 | 39 | with inject_services(configure): 40 | app = Application({'path': os.path.realpath(os.path.dirname(__file__) + '/_files/')}, name='myapp') 41 | config = yield app.load() 42 | 43 | assert isinstance(config, YamlConfig) 44 | assert len(config.get_services()) == 1 45 | assert config.app_name == 'myapp' 46 | 47 | service = config.get_services()['controller.myapp'] 48 | assert isinstance(service, Service) 49 | assert isinstance(service.image_builder, DockerfileImageBuilder) 50 | 51 | assert service.is_inspected() 52 | 53 | 54 | def test_internal_containers(): 55 | 56 | ac = ApplicationController() 57 | 58 | ac.mark_internal('123') 59 | 60 | assert ac.is_internal('123') 61 | 62 | assert ac.is_internal('124') is False 63 | assert ac.is_internal(None) is False 64 | 65 | 66 | @pytest.inlineCallbacks 67 | def test_app_controller(): 68 | 69 | def timeout(): 70 | print('Can not connect to redis!') 71 | reactor.stop() 72 | 73 | redis = yield txtimeout(txredisapi.Connection(dbid=2), 2, timeout) 74 | yield redis.flushdb() 75 | 76 | 77 | def configure(binder): 78 | binder.bind(txredisapi.Connection, redis) 79 | 80 | with inject_services(configure): 81 | controller = ApplicationController() 82 | 83 | with pytest.raises(AppDoesNotExist): 84 | yield controller.get('foo') 85 | 86 | r = yield controller.create('foo', {'path': 'some/path'}, skip_validation=True) 87 | assert isinstance(r, Application) 88 | assert r.name == 'foo' 89 | assert r.config['path'] == 'some/path' 90 | 91 | r = yield controller.get('foo') 92 | assert isinstance(r, Application) 93 | assert r.name == 'foo' 94 | assert r.config['path'] == 'some/path' 95 | 96 | r = yield controller.create('boo', {'path': 'other/path'}, skip_validation=True) 97 | assert isinstance(r, Application) 98 | assert r.name == 'boo' 99 | assert r.config['path'] == 'other/path' 100 | 101 | mockapp = flexmock() 102 | flexmock(Application).new_instances(mockapp) 103 | mockapp.should_receive('load').with_args(need_details=True).and_return(defer.succeed({'foo': 'bar'})) 104 | 105 | r = yield controller.list() 106 | 107 | assert isinstance(r, list) 108 | assert len(r) == 2 109 | for app in r: 110 | assert app == {'foo': 'bar'} 111 | 112 | yield controller.remove('foo') 113 | 114 | with pytest.raises(AppDoesNotExist): 115 | yield controller.get('foo') 116 | -------------------------------------------------------------------------------- /docs/source/guide/2_hello_app.rst: -------------------------------------------------------------------------------- 1 | 2 | ============================================ 3 | Deploying simple 1-container application 4 | ============================================ 5 | 6 | .. note:: 7 | 8 | Sources of this example are at https://github.com/modera/mcloud-samples/tree/master/hello 9 | 10 | We deploy simple application with nginx as web-server an single html page. 11 | 12 | Here is how our configuration will look like: 13 | 14 | .. uml:: 15 | 16 | cloud Internet { 17 | 18 | } 19 | 20 | package Docker { 21 | [Haproxy] << Load Balancer >> 22 | 23 | [nginx.myapp] 24 | 25 | Haproxy ..left..> nginx.myapp 26 | 27 | } 28 | 29 | Internet ..> Haproxy 30 | 31 | 32 | Initial configuration 33 | ------------------------ 34 | 35 | mcloud.yml:: 36 | 37 | web: 38 | web: 80 39 | image: nginx:latest 40 | 41 | volumes: 42 | public: /usr/share/nginx/html 43 | 44 | 45 | "web: 80" - means that this container will provide a web service on port 80. 46 | 47 | Image - docker image from https://registry.hub.docker.com/_/nginx/ 48 | 49 | volumes - defines that directory public will be mapped into "/usr/share/nginx/html" on nginx container. 50 | 51 | Application start 52 | -------------------- 53 | 54 | Let's start the application:: 55 | 56 | $ git clone git@github.com:modera/mcloud-samples.git 57 | $ cd mcloud-samples/hello 58 | $ mcloud start --init 59 | 60 | Using folder name as application name: hello 61 | 62 | [125] Starting application 63 | [125] Got response 64 | [125] Service web.hello is not created. Creating 65 | 66 | ************************************************** 67 | 68 | Service web.hello 69 | 70 | ************************************************** 71 | [125] Service web.hello is not running. Starting 72 | [125][web.hello] Starting service 73 | [125][web.hello] Service resolve by name result: 97386c42e961707d1a65f34c0181aec401b82c0e6d0db0c53eccf841085045aa 74 | [125][web.hello] Starting service... 75 | Startng container. config: {'ExtraHosts': [u'web:None'], 'Binds': [u'/home/alex/dev/mcloud-samples/hello/public:/usr/share/nginx/html', u'/root/.mcloud/volumes/web.hello/_var_cache_nginx:/var/cache/nginx']} 76 | Emit startup event 77 | Call start listener 78 | Updating haproxy configUpdating haproxy config on deployment localupdated local - OKUpdating container list 79 | result: u'Done.' 80 | 81 | Now we can check application status:: 82 | 83 | $ mcloud status 84 | 85 | Using folder name as application name: hello 86 | 87 | 88 | +--------------+--------+-------------+-------+--------+-----------------------+-------------------------+ 89 | | Service name | status | ip | cpu % | memory | volumes | public urls | 90 | +--------------+--------+-------------+-------+--------+-----------------------+-------------------------+ 91 | | web.hello | ON | 172.17.1.96 | 0.00% | 0M | /var/cache/nginx | http://hello.mcloud.lh/ | 92 | | | | | | | /usr/share/nginx/html | | 93 | +--------------+--------+-------------+-------+--------+-----------------------+-------------------------+ 94 | 95 | We can see our application with it's only container is running and have public url. 96 | Ctrl + click on it, to open it in browser. 97 | 98 | .. note:: 99 | 100 | You may notice that, mcloud shows "Using folder name as application name: hello" when starting application. You can 101 | read about application naming conventions in CLI reference 102 | 103 | Application names & basic commands 104 | ------------------------------------- 105 | 106 | You 107 | 108 | If you don't see "Hello, mcloud!", then go back to Installation chapter to make sure haproxy & dnsmasq are configured correctly. 109 | 110 | -------------------------------------------------------------------------------- /mcloud/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import signal 5 | import traceback 6 | 7 | from bashutils.colors import color_text 8 | import inject 9 | from mcloud.interrupt import InterruptManager 10 | from mcloud.remote import TaskFailure 11 | 12 | from mcloud.rpc_client import arg_parser, subparsers, ApiRpcClient, ClientProcessInterruptHandler 13 | from mcloud.shell import mcloud_shell 14 | from confire import Configuration 15 | from twisted.internet import reactor, defer 16 | from twisted.internet.defer import inlineCallbacks 17 | from twisted.python import log 18 | 19 | 20 | class ReactorInterruptHandler(object): 21 | 22 | def interrupt(self, last=None): 23 | reactor.callFromThread(reactor.stop) 24 | 25 | def main(argv): 26 | console_handler = logging.StreamHandler(stream=sys.stderr) 27 | console_handler.setFormatter(logging.Formatter()) 28 | console_handler.setLevel(logging.DEBUG) 29 | 30 | root_logger = logging.getLogger() 31 | root_logger.addHandler(console_handler) 32 | root_logger.setLevel(logging.INFO) 33 | root_logger.debug('Logger initialized') 34 | 35 | logging.getLogger("requests").propagate = False 36 | 37 | class SslConfiguration(Configuration): 38 | enabled = False 39 | key = '/etc/mcloud/ssl.key' 40 | cert = '/etc/mcloud/ssl.crt' 41 | 42 | class MyAppConfiguration(Configuration): 43 | 44 | CONF_PATHS = [ 45 | '/etc/mcloud/mcloud-client.yml', 46 | # os.path.expanduser('~/.myapp.yaml'), 47 | # os.path.abspath('conf/myapp.yaml') 48 | ] 49 | 50 | haproxy = False 51 | 52 | ssl = SslConfiguration() 53 | 54 | settings = MyAppConfiguration.load() 55 | 56 | interrupt_manager = InterruptManager() 57 | interrupt_manager.append(ReactorInterruptHandler()) 58 | interrupt_manager.register_interupt_handler() 59 | 60 | def my_config(binder): 61 | binder.bind('settings', settings) 62 | binder.bind('interrupt_manager', interrupt_manager) 63 | 64 | # Configure a shared injector. 65 | inject.configure(my_config) 66 | 67 | # client = ApiRpcClient(host=args.host, settings=settings) 68 | # subparsers.add_parser('!booo', help='Deploy application') 69 | 70 | if len(argv) == 2 and ('shell' == argv[1] or '@' in argv[1]): 71 | mcloud_shell(argv[1] if '@' in argv[1] else None) 72 | reactor.run() 73 | 74 | elif len(argv) == 1: 75 | arg_parser.print_help() 76 | sys.exit(2) 77 | 78 | else: 79 | args = arg_parser.parse_args() 80 | 81 | if args.verbose: 82 | log.startLogging(sys.stdout) 83 | 84 | args.argv0 = argv[0] 85 | 86 | if isinstance(args.func, str): 87 | 88 | log.msg('Starting task: %s' % args.func) 89 | 90 | @inlineCallbacks 91 | def call_command(): 92 | client = ApiRpcClient(host=args.host, settings=settings) 93 | interrupt_manager.append(ClientProcessInterruptHandler(client)) 94 | 95 | try: 96 | yield getattr(client, args.func)(**vars(args)) 97 | except Exception as e: 98 | label = type(e) 99 | if isinstance(e, ValueError): 100 | label = 'error' 101 | else: 102 | label = str(label) 103 | 104 | print '\n %s: %s\n' % ( 105 | color_text(label, color='cyan'), 106 | color_text(str(e), color='yellow'), 107 | ) 108 | 109 | interrupt_manager.manual_interrupt() 110 | 111 | call_command() 112 | reactor.run() 113 | 114 | else: 115 | ret = args.func(**vars(args)) 116 | 117 | if isinstance(ret, defer.Deferred): 118 | def clb(*args): 119 | reactor.callFromThread(reactor.stop) 120 | ret.addCallback(clb) 121 | 122 | reactor.run() 123 | 124 | 125 | def entry_point(): 126 | """Zero-argument entry point for use with setuptools/distribute.""" 127 | raise SystemExit(main(sys.argv)) 128 | 129 | 130 | if __name__ == '__main__': 131 | entry_point() 132 | 133 | -------------------------------------------------------------------------------- /mcloud/plugins/dns.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import inject 3 | from mcloud.application import ApplicationController 4 | 5 | from mcloud.events import EventBus 6 | from mcloud.plugins import Plugin 7 | from mcloud.service import Service 8 | 9 | from mcloud.container import InlineDockerfileImageBuilder 10 | 11 | from twisted.internet.defer import inlineCallbacks 12 | import txredisapi 13 | from twisted.python import log 14 | 15 | logger = logging.getLogger('mcloud.plugin.dns') 16 | import logging 17 | import inject 18 | from mcloud.application import ApplicationController 19 | from mcloud.events import EventBus 20 | from mcloud.plugin import IMcloudPlugin 21 | from mcloud.plugins import Plugin 22 | from mcloud.service import IServiceBuilder 23 | from twisted.internet.defer import inlineCallbacks 24 | import txredisapi 25 | from twisted.python import log 26 | from zope.interface import implements 27 | 28 | 29 | class DnsPlugin(Plugin): 30 | implements(IMcloudPlugin, IServiceBuilder) 31 | 32 | 33 | eb = inject.attr(EventBus) 34 | app_controller = inject.attr(ApplicationController) 35 | redis = inject.attr(txredisapi.Connection) 36 | settings = inject.attr('settings') 37 | host_ip = inject.attr('host-ip') 38 | dns_search_suffix = inject.attr('dns-search-suffix') 39 | """ @var McloudConfiguration """ 40 | 41 | @inlineCallbacks 42 | def dump(self, apps_list): 43 | apps = { 44 | 'mcloud.lh': self.host_ip 45 | } 46 | 47 | for app in apps_list: 48 | for service in app['services']: 49 | apps[service['fullname']] = service['ip'] 50 | 51 | if 'web_service' in app and app['web_service']: 52 | apps[app['fullname']] = app['web_ip'] 53 | 54 | if 'public_urls' in app and app['public_urls'] and 'web_ip' in app: 55 | for target in app['public_urls']: 56 | if not target['service']: 57 | apps[target['url']] = app['web_ip'] 58 | else: 59 | for service in app['services']: 60 | if service['shortname'] == target['service']: 61 | apps[target['url']] = service['ip'] 62 | 63 | log.msg('Installing new dns list: %s' % str(apps)) 64 | 65 | yield self.redis.delete('domain') 66 | 67 | if len(apps) > 1: 68 | yield self.redis.hmset('domain', apps) 69 | elif len(apps) == 1: 70 | yield self.redis.hset('domain', apps.keys()[0], apps.values()[0]) 71 | 72 | log.msg('Restarting dnsmasq') 73 | 74 | # extra options 75 | cmd = [] 76 | for url, ip in apps.items(): 77 | cmd.append('--host-record=%s,%s' % (url, ip)) 78 | 79 | 80 | self.dnsmasq = Service() 81 | self.dnsmasq.name = 'mcloud_dnsmasq' 82 | self.dnsmasq.image_builder = InlineDockerfileImageBuilder(source=""" 83 | FROM ubuntu:14.04 84 | RUN apt-get update && apt-get install -y dnsmasq dnsutils && apt-get clean 85 | CMD dnsmasq -k %s --server=8.8.8.8 -u root 86 | """ % ' '.join(cmd)) 87 | 88 | self.dnsmasq.ports = [ 89 | '53/tcp:%s_53' % self.settings.dns_ip, 90 | '53/udp:%s_53' % self.settings.dns_ip, 91 | ] 92 | 93 | yield self.dnsmasq.create() 94 | self.app_controller.mark_internal(self.dnsmasq.id) 95 | 96 | yield self.dnsmasq.rebuild() 97 | 98 | # with open('/etc/resolv.conf', 'w+') as f: 99 | # f.write('nameserver %s\n' % self.host_ip) 100 | # f.write('nameserver 8.8.8.8') 101 | 102 | 103 | 104 | 105 | def configure_container_on_create(self, service, config): 106 | pass 107 | 108 | 109 | # @inlineCallbacks 110 | def configure_container_on_start(self, service, config): 111 | pass 112 | # config.update({ 113 | # "Dns": [self.host_ip], 114 | # "DnsSearch": '%s.%s' % (service.app_name, self.dns_search_suffix) 115 | # }) 116 | 117 | 118 | # @inlineCallbacks 119 | def setup(self): 120 | pass 121 | # self.eb.on('containers.updated', self.containers_updated) 122 | # log.msg('Dns plugin started') 123 | # 124 | # yield self.containers_updated() 125 | 126 | @inlineCallbacks 127 | def containers_updated(self, *args, **kwargs): 128 | data = yield self.app_controller.list() 129 | self.dump(data) 130 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | import sys 5 | import imp 6 | 7 | from setuptools import setup, find_packages 8 | 9 | 10 | CODE_DIRECTORY = 'mcloud' 11 | DOCS_DIRECTORY = 'docs' 12 | TESTS_DIRECTORY = 'tests' 13 | 14 | # Import metadata. Normally this would just be: 15 | # 16 | # from mcloud import metadata 17 | # 18 | # However, when we do this, we also import `mcloud/__init__.py'. If this 19 | # imports names from some other modules and these modules have third-party 20 | # dependencies that need installing (which happens after this file is run), the 21 | # script will crash. What we do instead is to load the metadata module by path 22 | # instead, effectively side-stepping the dependency problem. Please make sure 23 | # metadata has no dependencies, otherwise they will need to be added to 24 | # the setup_requires keyword. 25 | metadata = imp.load_source( 26 | 'metadata', os.path.join(CODE_DIRECTORY, 'metadata.py')) 27 | 28 | # define install_requires for specific Python versions 29 | python_version_specific_requires = [] 30 | 31 | # as of Python >= 2.7 and >= 3.2, the argparse module is maintained within 32 | # the Python standard library, otherwise we install it as a separate package 33 | if sys.version_info < (2, 7): 34 | python_version_specific_requires.append('argparse') 35 | 36 | 37 | # See here for more options: 38 | # 39 | setup( 40 | name=metadata.package, 41 | version=metadata.version, 42 | author=metadata.authors[0], 43 | author_email=metadata.emails[0], 44 | maintainer=metadata.authors[0], 45 | maintainer_email=metadata.emails[0], 46 | url=metadata.url, 47 | description=metadata.description, 48 | long_description=open('README.rst').read(), 49 | # Find a list of classifiers here: 50 | # 51 | classifiers=[ 52 | 'Development Status :: 4 - Beta', 53 | 'Environment :: Console', 54 | 'Intended Audience :: Developers', 55 | 'Intended Audience :: System Administrators', 56 | 'License :: OSI Approved :: Apache Software License', 57 | 'Natural Language :: English', 58 | 'Operating System :: OS Independent', 59 | 'Programming Language :: Python :: 2.6', 60 | 'Programming Language :: Python :: 2.7', 61 | 'Programming Language :: Python :: 3.3', 62 | 'Programming Language :: Python :: Implementation :: PyPy', 63 | 'Topic :: Documentation', 64 | 'Topic :: Software Development :: Libraries :: Python Modules', 65 | 'Topic :: System :: Installation/Setup', 66 | 'Topic :: System :: Software Distribution', 67 | ], 68 | packages=find_packages(exclude=(TESTS_DIRECTORY,)), 69 | install_requires=[ 70 | # 'klein', 71 | 'txredisapi', 72 | 'inject', 73 | 'voluptuous', 74 | 'treq', 75 | # 'pyOpenSSL', 76 | 'PyYAML', 77 | # 'service-identity', 78 | 'netifaces', 79 | 'pynetinfo', 80 | # 'reqeuests', 81 | # 'werkzeug', 82 | 'autobahn==0.8.11', 83 | # 'pyasn1-modules', 84 | # 'characteristic', 85 | # 'cryptography>=0.7', 86 | 'confire', 87 | 'zope.interface', 88 | 'Twisted==14.0.2', 89 | 90 | # [client] 91 | 'readline', 92 | 'bashutils', 93 | 'texttable', 94 | 'pprintpp', 95 | 'prettytable', 96 | ] + python_version_specific_requires, 97 | 98 | # Allow tests to be run with `python setup.py test'. 99 | tests_require=[ 100 | 'pytest==2.5.1', 101 | 'mock==1.0.1', 102 | 'flake8==2.1.0', 103 | ], 104 | 105 | # extras_require = { 106 | # 'client': [ 107 | # 'readline', 108 | # 'bashutils', 109 | # 'texttable', 110 | # 'pprintpp', 111 | # 'prettytable', 112 | # 113 | # ] 114 | # }, 115 | 116 | zip_safe=False, # don't use eggs 117 | entry_points={ 118 | 'console_scripts': [ 119 | # 'mcloud = mcloud.main:entry_point [client]', 120 | 'mcloud = mcloud.main:entry_point', 121 | 'mcloud-server = mcloud.rpc_server:entry_point' 122 | ], 123 | 124 | # core plugins 125 | 'mcloud_plugins': [ 126 | 'hosts = mcloud.plugins.hosts:HostsPlugin', 127 | # 'dns = mcloud.plugins.dns:DnsPlugin', 128 | ] 129 | }, 130 | include_package_data = True, 131 | ) 132 | -------------------------------------------------------------------------------- /mcloud/static_old/assets/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | var mcloudApp = angular.module('mcloudApp', []); 3 | 4 | 5 | mcloudApp.controller('McloudAppsCtrl', function ($scope, $interval) { 6 | var io = new McloudIO(location.hostname, location.port); 7 | 8 | $scope.selection = { 9 | app: 'new', 10 | service: null, 11 | service_tab: null 12 | }; 13 | 14 | $scope.service_tasks = []; 15 | 16 | $scope.kill_service_tasks = function() { 17 | angular.forEach($scope.service_tasks, function(task_id) { 18 | console.log('kill', task_id); 19 | io.kill(task_id) 20 | }); 21 | $scope.service_tasks = []; 22 | }; 23 | 24 | $scope.select_app = function(app) { 25 | $scope.kill_service_tasks(); 26 | 27 | $scope.selection.app = app; 28 | $scope.selection.service = null; 29 | }; 30 | 31 | $scope.select_service = function(service) { 32 | $scope.kill_service_tasks(); 33 | 34 | if ($scope.selection.service && $scope.selection.service.log_terminal) { 35 | $scope.selection.service.log_terminal.destroy(); 36 | } 37 | 38 | if ($scope.selection.service && $scope.selection.service.run_terminal) { 39 | $scope.selection.service.run_terminal.destroy(); 40 | } 41 | 42 | $scope.selection.service = service; 43 | 44 | if ($scope.selection.service_tab == 'inspect' || ! $scope.selection.service_tab) { 45 | $scope.service_inspect(service); 46 | } 47 | 48 | if ($scope.selection.service_tab == 'logs') { 49 | $scope.service_logs(service); 50 | } 51 | }; 52 | 53 | $scope.service_inspect = function(service) { 54 | $scope.selection.service_tab = 'inspect'; 55 | 56 | io.inspect($scope.selection.app.name, $scope.selection.service.shortname).then(function(result) { 57 | service.inspect = result; 58 | $scope.$apply(); 59 | }); 60 | }; 61 | 62 | $scope.service_logs = function(service) { 63 | $scope.selection.service_tab = 'logs'; 64 | 65 | if (!service.log_terminal) { 66 | service.log_terminal = new Terminal({ 67 | cols: 80, 68 | rows: 24, 69 | screenKeys: false 70 | }); 71 | } 72 | 73 | io.logs(service.name).then(function(task) { 74 | service.log_terminal.reset(); 75 | $scope.service_tasks.push(task.id); 76 | 77 | task.on('progress', function(data) { 78 | if (service.log_terminal) { 79 | service.log_terminal.writeln(data); 80 | } 81 | }); 82 | 83 | $scope.$apply(); 84 | }); 85 | }; 86 | 87 | $scope.service_run = function(service) { 88 | $scope.selection.service_tab = 'run'; 89 | 90 | if (!service.run_terminal) { 91 | service.run_terminal = new Terminal({ 92 | cols: 80, 93 | rows: 24, 94 | screenKeys: true 95 | }); 96 | } 97 | 98 | io.run(service.name, 'bash', 80, 40).then(function(task) { 99 | service.run_terminal.reset(); 100 | 101 | $scope.service_tasks.push(task.id); 102 | 103 | task.on('stdout', function(data) { 104 | service.run_terminal.write(atob(data)); 105 | }); 106 | 107 | service.run_terminal.on('data', function(data) { 108 | task.stdin(btoa(data)); 109 | }); 110 | 111 | $scope.$apply(); 112 | }); 113 | }; 114 | 115 | $scope.update_apps = function() { 116 | io.list().then(function(result) { 117 | $scope.apps = result; 118 | 119 | if ($scope.selection.app) { 120 | angular.forEach($scope.apps, function(app) { 121 | if (app.name == $scope.selection.app.name) { 122 | $scope.selection.app = app; 123 | } 124 | }); 125 | } 126 | 127 | $scope.$apply(); 128 | }); 129 | }; 130 | 131 | $scope.update_apps(); 132 | 133 | }); 134 | 135 | mcloudApp.directive('xterm', function() { 136 | console.log('Registred'); 137 | 138 | function link(scope, element, attrs) { 139 | var term = null; 140 | console.log('Directive works!'); 141 | 142 | scope.$watch(attrs.term, function (value){ 143 | term = value; 144 | 145 | console.log(term); 146 | 147 | if (term) { 148 | term.open(element[0]); 149 | } 150 | 151 | }); 152 | } 153 | 154 | return { 155 | restrict: 'E', 156 | link: link 157 | }; 158 | }); -------------------------------------------------------------------------------- /mcloud/static_old/components/reconnectingWebsocket/reconnecting-websockets.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for reconnecting-websocket 2 | // Project: https://github.com/joewalnes/reconnecting-websocket 3 | // Definitions by: Drew Noakes 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | interface OpenEvent extends Event 7 | { 8 | /** If true, we have reconnected, otherwise this is the initial connection attempt. */ 9 | isReconnect: boolean; 10 | } 11 | 12 | /** 13 | * Behaves as a regular WebSocket class and adds logic for reconnecting when disconnected. 14 | */ 15 | declare class ReconnectingWebSocket 16 | { 17 | /** Whether all instances of ReconnectingWebSocket should log debug messages. */ 18 | static debugAll: boolean; 19 | 20 | /** Whether this instance should log debug messages. */ 21 | public debug: boolean; 22 | 23 | /** The number of milliseconds to delay before attempting to reconnect. */ 24 | public reconnectInterval: number; 25 | 26 | /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */ 27 | public reconnectDecay: number; 28 | 29 | /** The number of attempted reconnects since starting, or the last successful connection. */ 30 | public reconnectAttempts: number; 31 | 32 | /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */ 33 | public timeoutInterval: number; 34 | 35 | /** An event listener to be called when a connection begins being attempted. */ 36 | onconnecting: (ev: Event) => any; 37 | 38 | addEventListener(type: "connecting", listener: (ev: Event) => any, useCapture?: boolean): void; 39 | 40 | /** 41 | * Additional public API method to refresh the connection if still open (close, re-open). 42 | * For example, if the app suspects bad data / missed heart beats, it can try to refresh. 43 | */ 44 | refresh(): void; 45 | 46 | ///////////////////////////////////////////////////////////////////////////////////////////////////////// 47 | 48 | /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */ 49 | public url: string; 50 | 51 | /** 52 | * A string indicating the name of the sub-protocol the server selected; this will be one of 53 | * the strings specified in the protocols parameter when creating the WebSocket object. 54 | * Read only. 55 | */ 56 | public protocol: string; 57 | 58 | /** 59 | * The current state of the connection. 60 | * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED 61 | * Read only. 62 | */ 63 | public readyState: number; 64 | 65 | /** 66 | * 67 | * @param url the absolute URL of the service, eg: ws://host:port/path 68 | * @param protocols an optional array of protocol strings 69 | */ 70 | constructor(url: string, protocols?: string[]); 71 | 72 | /** 73 | * An event listener to be called when the WebSocket connection's readyState changes to OPEN; 74 | * this indicates that the connection is ready to send and receive data. 75 | */ 76 | onopen: (ev: Event) => any; 77 | 78 | /** An event listener to be called when a message is received from the server. */ 79 | onmessage: (ev: any) => any; 80 | 81 | /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */ 82 | onclose: (ev: CloseEvent) => any; 83 | 84 | /** An event listener to be called when an error occurs. */ 85 | onerror: (ev: ErrorEvent) => any; 86 | 87 | addEventListener(type: "open", listener: (ev: Event) => any, useCapture?: boolean): void; 88 | addEventListener(type: "message", listener: (ev: any) => any, useCapture?: boolean): void; 89 | addEventListener(type: "close", listener: (ev: CloseEvent) => any, useCapture?: boolean): void; 90 | addEventListener(type: "error", listener: (ev: ErrorEvent) => any, useCapture?: boolean): void; 91 | 92 | addEventListener(type: string, listener: EventListener, useCapture?: boolean): void; 93 | removeEventListener(name: string, listener: (ev: Event) => any, useCapture?: boolean); 94 | dispatchEvent(event: Event); 95 | 96 | /** 97 | * Transmits data to the server over the WebSocket connection. 98 | * 99 | * @param data a text string, ArrayBuffer or Blob to send to the server. 100 | */ 101 | send(data: any): void; 102 | 103 | /** 104 | * Closes the WebSocket connection or connection attempt, if any. 105 | * If the connection is already CLOSED, this method does nothing. 106 | */ 107 | close(code?: number, reason?: string): void; 108 | 109 | static CONNECTING: number; 110 | static OPEN: number; 111 | static CLOSING: number; 112 | static CLOSED: number; 113 | } 114 | -------------------------------------------------------------------------------- /mcloud/shell.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import readline 3 | from autobahn.twisted.util import sleep 4 | 5 | from bashutils.colors import color_text 6 | import inject 7 | from mcloud.interrupt import InterruptCancel 8 | from mcloud.rpc_client import subparsers, arg_parser, ApiRpcClient, ClientProcessInterruptHandler 9 | import os 10 | from twisted.internet import reactor 11 | from twisted.internet.defer import inlineCallbacks 12 | 13 | 14 | def green(text): 15 | print(color_text(text, color='green')) 16 | 17 | def yellow(text): 18 | print(color_text(text, color='blue', bcolor='yellow')) 19 | 20 | def info(text): 21 | print() 22 | 23 | 24 | class ShellCancelInterruptHandler(object): 25 | 26 | def interrupt(self, last=None): 27 | if last is None: 28 | print('Hit Ctrl+D for exit.') 29 | raise InterruptCancel() 30 | 31 | 32 | @inlineCallbacks 33 | def mcloud_shell(host_ref=None): 34 | 35 | settings = inject.instance('settings') 36 | interrupt_manager = inject.instance('interrupt_manager') 37 | 38 | readline.parse_and_bind('tab: complete') 39 | 40 | if host_ref: 41 | app, host = host_ref.split('@') 42 | state = { 43 | 'app': app, 44 | 'host': host, 45 | } 46 | else: 47 | state = { 48 | 'app': None, 49 | 'host': 'me', 50 | } 51 | 52 | def use(name, **kwargs): 53 | if '@' in name: 54 | app, host = name.split('@') 55 | if host.strip() == '': 56 | host = 'me' 57 | if app.strip() == '': 58 | app = None 59 | 60 | state['app'] = app 61 | state['host'] = host 62 | else: 63 | state['app'] = name 64 | 65 | cmd = subparsers.add_parser('use') 66 | cmd.add_argument('name', help='Application name', default=None, nargs='?') 67 | cmd.set_defaults(func=use) 68 | 69 | from mcloud.logo import logo 70 | print(logo) 71 | 72 | histfile = os.path.join(os.path.expanduser("~"), ".mcloud_history") 73 | try: 74 | readline.read_history_file(histfile) 75 | except IOError: 76 | pass 77 | 78 | interrupt_manager.append(ShellCancelInterruptHandler()) # prevent stop reactor on Ctrl + C 79 | 80 | line = '' 81 | while line != 'exit': 82 | 83 | print('') 84 | prompt = 'mcloud: %s@%s> ' % (state['app'] or '~', state['host']) 85 | 86 | try: 87 | line = None 88 | 89 | yield sleep(0.05) 90 | 91 | line = raw_input(color_text(prompt, color='white', bcolor='blue') + ' ').strip() 92 | 93 | if line.startswith('!'): 94 | os.system(line[1:]) 95 | continue 96 | 97 | if line == '': 98 | continue 99 | 100 | if line == 'exit': 101 | break 102 | 103 | readline.write_history_file(histfile) 104 | 105 | params = line.split(' ') 106 | args = arg_parser.parse_args(params) 107 | 108 | args.argv0 = sys.argv[0] 109 | 110 | if args.host: 111 | host = args.host 112 | elif state['host'] == 'me' or not state['host']: 113 | # manual variable 114 | if 'MCLOUD_HOST' in os.environ: 115 | host = os.environ['MCLOUD_HOST'] 116 | 117 | # automatic when using docker container-link 118 | elif 'MCLOUD_PORT' in os.environ: 119 | host = os.environ['MCLOUD_PORT'] 120 | if host.startswith('tcp://'): 121 | host = host[6:] 122 | else: 123 | host = '127.0.0.1' 124 | 125 | else: 126 | host = state['host'] 127 | 128 | if ':' in host: 129 | host, port = host.split(':') 130 | else: 131 | port = 7080 132 | 133 | client = ApiRpcClient(host=host, port=port, settings=settings) 134 | interrupt_manager.append(ClientProcessInterruptHandler(client)) 135 | 136 | for key, val in state.items(): 137 | if not hasattr(args, key) or not getattr(args, key): 138 | setattr(args, key, val) 139 | 140 | if isinstance(args.func, str): 141 | yield getattr(client, args.func)(**vars(args)) 142 | else: 143 | yield args.func(**vars(args)) 144 | 145 | 146 | except SystemExit: 147 | pass 148 | 149 | except EOFError: 150 | print('') 151 | break 152 | 153 | except KeyboardInterrupt: 154 | print('') 155 | pass 156 | 157 | except Exception as e: 158 | print '\n %s\n' % color_text(e.message, color='yellow') 159 | 160 | reactor.callFromThread(reactor.stop) 161 | 162 | -------------------------------------------------------------------------------- /mcloud/events.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from twisted.internet import reactor, defer 4 | import txredisapi as redis 5 | from twisted.python import log 6 | from mcloud.util import txtimeout 7 | 8 | 9 | class EventBus(object): 10 | redis = None 11 | protocol = None 12 | 13 | def __init__(self, redis_connection): 14 | super(EventBus, self).__init__() 15 | self.redis = redis_connection 16 | 17 | def fire_event(self, event_name, data=None, *args, **kwargs): 18 | if not data: 19 | if kwargs: 20 | data = kwargs 21 | elif args: 22 | data = args 23 | 24 | if not isinstance(data, basestring): 25 | data = 'j:' + json.dumps(data) 26 | else: 27 | data = 'b:' + str(data) 28 | 29 | return self.redis.publish(event_name, data) 30 | 31 | 32 | def connect(self, host="127.0.0.1", port=6379): 33 | log.msg('Event bus connected') 34 | d = defer.Deferred() 35 | reactor.connectTCP(host, port, EventBusFactory(d, self)) 36 | return d 37 | 38 | def on(self, pattern, callback): 39 | if not self.protocol: 40 | raise Exception('Event bus is not connected yet!') 41 | self.protocol.on(pattern, callback) 42 | log.msg('Registered %s for channel: %s' % (callback, pattern)) 43 | 44 | def cancel(self, pattern, callback): 45 | if not self.protocol: 46 | raise Exception('Event bus is not connected yet!') 47 | self.protocol.cancel(pattern, callback) 48 | log.msg('unRegistered %s for channel: %s' % (callback, pattern)) 49 | 50 | def once(self, pattern, callback): 51 | if not self.protocol: 52 | raise Exception('Event bus is not connected yet!') 53 | 54 | def _once_and_remove(*args, **kwargs): 55 | self.protocol.cancel(pattern, _once_and_remove) 56 | callback(*args, **kwargs) 57 | 58 | self.protocol.on(pattern, _once_and_remove) 59 | log.msg('Registered %s for single invocation on channel: %s' % (callback, pattern)) 60 | 61 | def wait_for_event(self, pattern, timeout=False): 62 | d = defer.Deferred() 63 | 64 | def _on_message(channel, message): 65 | if not d.called: 66 | d.callback(message) 67 | 68 | self.on(pattern, _on_message) 69 | 70 | if not timeout == 0: 71 | return txtimeout(d, timeout, lambda: d.callback(None)) 72 | else: 73 | return d 74 | 75 | 76 | 77 | class EventBusProtocol(redis.SubscriberProtocol): 78 | callbacks = {} 79 | 80 | def on(self, pattern, callback): 81 | if not pattern in self.callbacks: 82 | self.callbacks[pattern] = [] 83 | 84 | if '*' in pattern: 85 | self.psubscribe(pattern) 86 | else: 87 | self.subscribe(pattern) 88 | 89 | self.callbacks[pattern].append(callback) 90 | 91 | def cancel(self, pattern, callback): 92 | if pattern in self.callbacks: 93 | self.callbacks[pattern].remove(callback) 94 | 95 | def connectionMade(self): 96 | self.factory.eb.protocol = self 97 | 98 | if self.factory.on_connect: 99 | self.factory.on_connect.callback(self) 100 | self.factory.on_connect = None 101 | # 102 | #print "waiting for messages..." 103 | #print "use the redis client to send messages:" 104 | #print "$ redis-cli publish zz test" 105 | #print "$ redis-cli publish foo.bar hello world" 106 | #self.subscribe("zz") 107 | #self.psubscribe("foo.*") 108 | #reactor.callLater(10, self.unsubscribe, "zz") 109 | #reactor.callLater(15, self.punsubscribe, "foo.*") 110 | 111 | # self.continueTrying = False 112 | # self.transport.loseConnection() 113 | 114 | def messageReceived(self, pattern, channel, message): 115 | if message.startswith('j:'): 116 | message = json.loads(message[2:]) 117 | else: 118 | message = message[2:] 119 | 120 | callbacks = [] 121 | if pattern and pattern in self.callbacks: 122 | callbacks = self.callbacks[pattern] 123 | elif channel and channel in self.callbacks: 124 | callbacks = self.callbacks[channel] 125 | 126 | for clb in callbacks: 127 | clb(channel, message) 128 | 129 | def connectionLost(self, reason): 130 | log.msg("Connection lost: %s" % reason) 131 | 132 | 133 | class EventBusFactory(redis.SubscriberFactory): 134 | maxDelay = 120 135 | continueTrying = True 136 | 137 | protocol = EventBusProtocol 138 | 139 | def __init__(self, on_connect, eb): 140 | redis.SubscriberFactory.__init__(self) 141 | self.on_connect = on_connect 142 | self.eb = eb 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /mcloud/static/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | angular-kickstart 7 | 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 | 40 | 41 |
42 | 43 | 44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 | 52 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere faucibus efficitur. 53 |
54 | 55 |
56 | 57 | 58 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere faucibus efficitur. 59 |
60 | 61 |
62 | 63 | 64 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere faucibus efficitur. 65 |
66 |
67 | 68 | 69 | Light 112sp 70 | 71 |
72 |

Use the AngularJS directives and services

73 | 74 |

75 | When you have downloaded all the dependencies you need to add dependencies on the LumX AngularJS module:
76 | angular.module('myModule', ['lumx']); 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 | -------------------------------------------------------------------------------- /tests/test_tasks.py: -------------------------------------------------------------------------------- 1 | from flexmock import flexmock 2 | from mcloud.application import ApplicationController, Application, AppDoesNotExist 3 | from mcloud.deployment import DeploymentController, Deployment 4 | from mcloud.tasks import TaskService 5 | from mcloud.util import inject_services, injector, txtimeout 6 | import pytest 7 | from twisted.internet import defer, reactor 8 | import txredisapi 9 | 10 | 11 | @pytest.mark.xfail 12 | def test_tasks_are_registered(): 13 | 14 | with injector({}): 15 | 16 | ts = TaskService() 17 | tasks = ts.collect_tasks() 18 | 19 | assert tasks['help'] == ts.task_help 20 | 21 | 22 | 23 | @pytest.inlineCallbacks 24 | def test_init_app_task(): 25 | 26 | ac = flexmock() 27 | 28 | def configure(binder): 29 | binder.bind(ApplicationController, ac) 30 | 31 | with inject_services(configure): 32 | 33 | ac.should_receive('get').and_raise(AppDoesNotExist) 34 | ac.should_receive('create').with_args('foo', {'path': 'some/path', 'deployment': None, 'source': 'foo', 'env': 'prod'}).and_return(defer.succeed(flexmock())) 35 | ac.should_receive('list').and_return(defer.succeed('result-of-list-operation')) 36 | 37 | ts = TaskService() 38 | 39 | r = yield ts.task_init(123123, 'foo', 'some/path', config='foo', env='prod') 40 | assert r is True 41 | 42 | 43 | @pytest.inlineCallbacks 44 | @pytest.mark.xfail 45 | def test_deployment_new_app_task_source(): 46 | 47 | dc = flexmock() 48 | 49 | def configure(binder): 50 | binder.bind(DeploymentController, dc) 51 | 52 | with inject_services(configure): 53 | 54 | dc.should_receive('new_app').with_args('baz', 'foo', {'source': 'foo: bar'}).and_return(defer.succeed(flexmock())).once() 55 | 56 | ts = TaskService() 57 | 58 | r = yield ts.task_deployment_new_app_source(123123, 'baz', 'foo', 'foo: bar') 59 | assert r is True 60 | 61 | 62 | @pytest.inlineCallbacks 63 | def test_list_app_task(): 64 | 65 | ac = flexmock() 66 | 67 | ac.should_receive('list').and_return(defer.succeed(['foo', 'bar'])).once() 68 | 69 | def configure(binder): 70 | binder.bind(ApplicationController, ac) 71 | 72 | with inject_services(configure): 73 | 74 | 75 | 76 | ts = TaskService() 77 | 78 | r = yield ts.task_list(123123) 79 | assert r == ['foo', 'bar'] 80 | 81 | 82 | @pytest.inlineCallbacks 83 | def test_push_task(): 84 | 85 | ac = flexmock() 86 | 87 | ac.should_receive('list').and_return(defer.succeed(['foo', 'bar'])).once() 88 | 89 | def configure(binder): 90 | binder.bind(ApplicationController, ac) 91 | 92 | with inject_services(configure): 93 | 94 | ts = TaskService() 95 | 96 | r = yield ts.task_list(123123) 97 | assert r == ['foo', 'bar'] 98 | 99 | 100 | @pytest.inlineCallbacks 101 | @pytest.mark.xfail 102 | def test_register_file(): 103 | 104 | rc = yield txredisapi.Connection(dbid=2) 105 | yield rc.flushdb() 106 | 107 | def configure(binder): 108 | binder.bind(txredisapi.Connection, rc) 109 | 110 | with inject_services(configure): 111 | 112 | ts = TaskService() 113 | 114 | r = yield ts.task_register_file(None) 115 | assert r == 1 116 | 117 | r = yield ts.task_register_file(None) 118 | assert r == 2 119 | 120 | r = yield ts.task_register_file(None) 121 | assert r == 3 122 | 123 | 124 | 125 | 126 | 127 | @pytest.inlineCallbacks 128 | @pytest.mark.xfail 129 | def test_list_deployments_task(): 130 | 131 | ac = flexmock() 132 | dc = flexmock() 133 | 134 | def configure(binder): 135 | binder.bind(ApplicationController, ac) 136 | binder.bind(DeploymentController, dc) 137 | 138 | with inject_services(configure): 139 | 140 | ac.should_receive('get').with_args('foo').and_return(defer.succeed(Application({'path': 'some/path'}))).once() 141 | dc.should_receive('list').and_return(defer.succeed([Deployment(public_domain='foo.bar', name='baz', apps=['foo'])])).once() 142 | 143 | ts = TaskService() 144 | 145 | r = yield ts.task_deployments(123123) 146 | 147 | 148 | assert r == [{ 149 | 'name': 'baz', 150 | 'public_domain': 'foo.bar', 151 | 'apps': [ 152 | { 153 | 'name': 'foo', 154 | 'config': {'path': 'some/path'} 155 | } 156 | ] 157 | }] 158 | 159 | @pytest.inlineCallbacks 160 | def test_set_var_task(): 161 | 162 | def timeout(): 163 | print('Can not connect to redis!') 164 | reactor.stop() 165 | 166 | redis = yield txtimeout(txredisapi.Connection(dbid=2), 2, timeout) 167 | yield redis.flushdb() 168 | 169 | def configure(binder): 170 | binder.bind(txredisapi.Connection, redis) 171 | 172 | with inject_services(configure): 173 | 174 | ts = TaskService() 175 | 176 | r = yield ts.task_list_vars(123123) 177 | assert r == {} 178 | 179 | r = yield ts.task_set_var(123123, 'boo', '123') 180 | assert r == {'boo': 123} 181 | 182 | r = yield ts.task_list_vars(123123) 183 | assert r == {'boo': 123} 184 | 185 | r = yield ts.task_set_var(123123, 'boo2', '1234') 186 | assert r == {'boo': 123, 'boo2': 1234} 187 | 188 | r = yield ts.task_rm_var(123123, 'boo') 189 | assert r == {'boo2': 1234} -------------------------------------------------------------------------------- /docs/source/use/app.rst: -------------------------------------------------------------------------------- 1 | 2 | ============================= 3 | Working with applications 4 | ============================= 5 | 6 | 7 | Application status 8 | ======================= 9 | 10 | 11 | List 12 | -------------- 13 | 14 | List shows all application list:: 15 | 16 | $ mcloud list [-f] 17 | 18 | Command have special flag "-f" (follow) that continuously print status report. 19 | 20 | 21 | Status 22 | -------------- 23 | 24 | Show status of application:: 25 | 26 | $ mcloud status app [-f] 27 | 28 | Command have special flag "-f" (follow) that continuously print status report. 29 | 30 | 31 | Application lifecycle 32 | ============================== 33 | 34 | .. uml:: 35 | 36 | (*) --> [init] Not created 37 | "Not created" --> [create] Created 38 | Created --> [destroy] "Not created" 39 | "Not created" --> [remove] (*) 40 | Created --> [start] Started 41 | Started --> [stop] Created 42 | 43 | .. note:: 44 | 45 | If aplication or service is in state that requires more actions to get desired state, those actions will 46 | be performed. Ex. if to **remove** Started application, then **stop**, **destroy** and **remove** commands will be 47 | executed sequentially. 48 | 49 | Service and application states 50 | ------------------------------------ 51 | 52 | Each service have same state as on diagram above. **List** command also shows application status that 53 | is best describing sum of all services statuses. 54 | 55 | Service naming convention (aka. ref) 56 | ------------------------------------- 57 | 58 | 59 | Application lifecycle commands operate in scope of an application or its services. 60 | 61 | They all have common syntax of:: 62 | 63 | $ mcloud {command} [service.][app] 64 | 65 | Command can be run also without service name, then action will be applied to 66 | entire application:: 67 | 68 | $ mcloud {command} app 69 | 70 | Also, application name may be ommitted will try to restore it from context (use folder name):: 71 | 72 | $ mcloud {command} service. 73 | 74 | 75 | Note the dot(".") at the end, it specify it's service name not the application. 76 | 77 | *Ref* maybe fully omitted as well:: 78 | 79 | $ mcloud {command} 80 | 81 | In this case command is applied to application with name same as folder name. 82 | 83 | In shell mode, current application may be set using "use" command. 84 | 85 | When service name is specified, command is executed on single container. When only application name given, command is applied on each container in order they appear in *mcloud.yml*. 86 | 87 | 88 | Commands 89 | ============ 90 | 91 | Init 92 | -------------- 93 | 94 | Initialize new application:: 95 | 96 | $ mcloud init [appname] [path] [--config] 97 | 98 | Accepts path as extra argument (by default current directory) 99 | app name is directory name by default. 100 | 101 | You can specify configuration file explicitly with --config. 102 | 103 | .. note:: 104 | There is shorthand "**start --init**", which initialize and start newly created application 105 | 106 | In case of remote server command will also upload files there. You may interrupt this process and complete it later manually. 107 | 108 | 109 | Remove 110 | -------------- 111 | 112 | Removes an application:: 113 | 114 | $ mcloud remove 115 | 116 | Command will destroy all containers and remove application from app-list. 117 | 118 | .. note:: This command still will not remove any application volumes (ex. /var/lib/mysql for mysql container). 119 | 120 | To remove application data completely, specify --scrub-data flag:: 121 | 122 | $ mcloud remove --scrub-data 123 | 124 | 125 | 126 | 127 | Create 128 | ---------- 129 | 130 | Create application containers without starting them. 131 | 132 | 133 | Start 134 | ---------- 135 | 136 | Start application containers, will trigger *create* for containers that are not created yet. Once all containers exist it starts them. 137 | ModeraClouds optional --iModeraCloudag that tells ModeraCloud to initialize the application if it's not there. 138 | 139 | 140 | Stop 141 | ---------- 142 | 143 | Stop application containers. 144 | 145 | 146 | Restart 147 | ---------- 148 | 149 | Runs *stop* sequentially on all services, then *start* again. 150 | 151 | 152 | Destroy 153 | ---------- 154 | 155 | Remove application containers. Triggers *stop* for running containers beforehand. 156 | 157 | Rebuild 158 | ---------- 159 | 160 | Runs *destroy* on all services. Then *start* again. 161 | 162 | 163 | 164 | Configuration change 165 | ====================== 166 | 167 | When changing mcloud.yml, changes are not immediately applied, instead saved 168 | version of mcloud.yml is used. To apply new changes, you need to say mcloud 169 | to apply those changes. 170 | 171 | Tos see difference between current config and in-memory mcloud.yml:: 172 | 173 | $ mcloud config appname --diff 174 | 175 | To apply changes:: 176 | 177 | $ mcloud config appname --update 178 | 179 | 180 | Examples:: 181 | 182 | $ mcloud config mysqapp # shows current configuration 183 | $ mcloud config mysqapp --set-env prod # set environment to prod 184 | $ mcloud config mysqapp --update # reloads configuration 185 | $ mcloud config mysqapp --diff # shows difference between current config and mcloud.yml file 186 | 187 | 188 | .. note:: 189 | 190 | Changes will be applied to the containers only after container rebuild. 191 | 192 | 193 | .. warning:: 194 | 195 | Mcloud will not remove the containers, if you remove them from config file. --------------------------------------------------------------------------------