├── .github └── FUNDING.yml ├── .gitignore ├── .idea ├── compiler.xml ├── composerJson.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── em.xml ├── encodings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── jpa-buddy.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ ├── list.xml │ └── start.xml └── vcs.xml ├── .readthedocs.yaml ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Thorfile ├── VERSION ├── bin ├── console ├── docker-sync ├── docker-sync-daemon └── docker-sync-stack ├── deploy_locally.sh ├── docker-sync.gemspec ├── docker_sync.iml ├── docs ├── Makefile ├── README.md ├── _static │ └── native_osx.png ├── advanced │ ├── how-it-works.rst │ ├── scripting.rst │ ├── sync-strategies.rst │ └── tips-and-tricks.rst ├── conf.py ├── getting-started │ ├── commands.rst │ ├── configuration.rst │ ├── installation.rst │ └── upgrade.rst ├── index.rst ├── make.bat ├── miscellaneous │ ├── alternatives.rst │ ├── changelog.rst │ ├── development-and-testing.rst │ └── performance.rst ├── requirements.txt └── troubleshooting │ ├── common-issues.rst │ ├── native-osx-troubleshooting.rst │ └── sync-stopping.rst ├── example ├── create-data │ ├── Dockerfile │ └── entrypoint.sh ├── data1 │ └── somefile.txt ├── data2 │ └── somefile.txt ├── data3 │ └── filefromhost.txt ├── data4 │ └── filefromhost.txt ├── default │ ├── app │ │ └── index.html │ ├── docker-compose-dev.yml │ ├── docker-compose.yml │ └── docker-sync.yml ├── docker-compose-dev.yml ├── docker-compose.yml └── docker-sync.yml ├── issue_template.md ├── lib ├── docker-sync.rb └── docker-sync │ ├── command.rb │ ├── compose.rb │ ├── config │ ├── config_locator.rb │ ├── config_serializer.rb │ ├── global_config.rb │ └── project_config.rb │ ├── dependencies.rb │ ├── dependencies │ ├── docker.rb │ ├── docker_driver.rb │ ├── fswatch.rb │ ├── package_manager.rb │ ├── package_managers │ │ ├── apt.rb │ │ ├── base.rb │ │ ├── brew.rb │ │ ├── none.rb │ │ ├── pkg.rb │ │ └── yum.rb │ ├── rsync.rb │ ├── unison.rb │ └── unox.rb │ ├── docker_compose_session.rb │ ├── environment.rb │ ├── execution.rb │ ├── sync_manager.rb │ ├── sync_process.rb │ ├── sync_strategy │ ├── native.rb │ ├── native_osx.rb │ ├── rsync.rb │ └── unison.rb │ ├── update_check.rb │ ├── upgrade_check.rb │ └── watch_strategy │ ├── dummy.rb │ ├── fswatch.rb │ ├── remotelogs.rb │ └── unison.rb ├── publish.sh ├── spec ├── fixtures │ ├── app_skeleton │ │ ├── Makefile │ │ ├── README.md │ │ └── src │ │ │ └── main.c │ ├── dummy │ │ └── docker-sync.yml │ ├── dynamic-configuration-dotenv │ │ ├── .env │ │ └── docker-sync.yml │ ├── mismatch_version │ │ └── docker-sync.yml │ ├── missing_syncs │ │ └── docker-sync.yml │ ├── missing_syncs_src │ │ └── docker-sync.yml │ ├── missing_syncs_sync_host_port │ │ └── docker-sync.yml │ ├── missing_version │ │ └── docker-sync.yml │ ├── native_osx │ │ └── docker-sync.yml │ ├── project_root │ │ ├── app │ │ │ └── .gitkeep │ │ └── docker-sync.yml │ ├── rsync │ │ └── docker-sync.yml │ ├── simplest │ │ ├── app │ │ │ └── .gitkeep │ │ └── docker-sync.yml │ └── unison │ │ └── docker-sync.yml ├── helpers │ ├── command_mocking_helpers.rb │ ├── docker_for_mac_config_check.rb │ ├── fixture_helpers.rb │ ├── raise_error_helpers.rb │ ├── replace_in_file.rb │ └── synchronization_helpers.rb ├── integration │ ├── native-osx_strategy_spec.rb │ └── version_spec.rb ├── lib │ └── docker-sync │ │ ├── command_spec.rb │ │ ├── dependencies │ │ ├── docker_driver_spec.rb │ │ ├── docker_spec.rb │ │ ├── fswatch_spec.rb │ │ ├── package_manager_spec.rb │ │ ├── package_managers │ │ │ ├── apt_spec.rb │ │ │ ├── brew_spec.rb │ │ │ ├── none_spec.rb │ │ │ └── yum_spec.rb │ │ ├── rsync_spec.rb │ │ ├── unison_spec.rb │ │ └── unox_spec.rb │ │ ├── dependencies_spec.rb │ │ ├── docker_compose_session_spec.rb │ │ ├── global_config_spec.rb │ │ └── project_config_spec.rb ├── shared_examples │ ├── dependencies_shared_example.rb │ ├── package_managers_shared_examples.rb │ └── synchronization_shared_examples.rb └── spec_helper.rb └── tasks ├── daemon └── daemon.thor ├── stack └── stack.thor └── sync └── sync.thor /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: eugenmayer 4 | patreon: eugenmayer 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | example/default/.docker-sync 2 | .idea/workspace.xml 3 | docker-sync-boilerplate 4 | *.sw* 5 | # Created by .ignore support plugin (hsz.mobi) 6 | ### Ruby template 7 | *.gem 8 | *.rbc 9 | /.config 10 | /coverage/ 11 | /InstalledFiles 12 | /pkg/ 13 | /spec/reports/ 14 | /spec/examples.txt 15 | /.rspec 16 | /example/tmp/ 17 | /example/version_tmp/ 18 | /tmp/ 19 | 20 | # Used by dotenv library to load environment variables. 21 | # .env 22 | 23 | ## Specific to RubyMotion: 24 | .dat* 25 | .repl_history 26 | build/ 27 | *.bridgesupport 28 | build-iPhoneOS/ 29 | build-iPhoneSimulator/ 30 | 31 | ## Specific to RubyMotion (use of CocoaPods): 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # vendor/Pods/ 38 | 39 | ## Documentation cache and generated files: 40 | /.yardoc/ 41 | /_yardoc/ 42 | /doc/ 43 | /rdoc/ 44 | /docs/_build/ 45 | 46 | ## Environment normalization: 47 | /.bundle/ 48 | /vendor/bundle 49 | /lib/bundler/man/ 50 | 51 | # for a library or gem, you might want to ignore these files since the code is 52 | # intended to run in multiple environments; otherwise, check them in: 53 | # Gemfile.lock 54 | # .ruby-version 55 | # .ruby-gemset 56 | 57 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 58 | .rvmrc 59 | 60 | # ctags 61 | tags 62 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/composerJson.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/em.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dotenv 5 | fswatch 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/jpa-buddy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/runConfigurations/start.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-22.04 4 | tools: 5 | python: "3.12" 6 | 7 | sphinx: 8 | configuration: docs/conf.py 9 | 10 | python: 11 | install: 12 | - requirements: docs/requirements.txt 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | arch: 4 | - amd64 5 | - arm64 6 | 7 | rvm: 8 | - 2.4 9 | - 2.7 10 | - 3.1.2 11 | 12 | os: 13 | - linux 14 | # since there is no docker support due to license issues under OSX, running there does not make any sense 15 | # - osx 16 | 17 | services: docker 18 | 19 | jobs: 20 | exclude: 21 | # does not work - rvm cannot install ruby 3.x under arm64 (not investigated further) 22 | - rvm: 3.1.2 23 | arch: arm64 24 | 25 | # ruby 3 somehow cannot be installed on osx either 26 | - rvm: 3.1.2 27 | os: osx 28 | 29 | before_install: 30 | - gem install bundler:2.2.15 31 | - docker pull ghcr.io/eugenmayer/unison:2.52.1-4.12.0 32 | - docker pull eugenmayer/rsync 33 | 34 | script: bundle exec rspec --format=documentation 35 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem 'rspec' 7 | gem 'rspec-bash' 8 | # last version supports ruby 2.5 9 | gem 'activesupport', '~>6.1.7' 10 | gem 'os', '>= 1.0.0' 11 | gem 'rdoc', '<=6.4.0' 12 | gem 'minitest', '<=5.16.3' 13 | end 14 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | docker-sync (1.0.4) 5 | daemons (~> 1.4, >= 1.4.1) 6 | dotenv (~> 2.8, >= 2.8.1) 7 | gem_update_checker (~> 0.2.0, >= 0.2.0) 8 | os (>= 1.0.0) 9 | terminal-notifier (= 2.0.0) 10 | thor (~> 1.2, >= 1.2.1) 11 | 12 | GEM 13 | remote: https://rubygems.org/ 14 | specs: 15 | activesupport (6.1.7.3) 16 | concurrent-ruby (~> 1.0, >= 1.0.2) 17 | i18n (>= 1.6, < 2) 18 | minitest (>= 5.1) 19 | tzinfo (~> 2.0) 20 | zeitwerk (~> 2.3) 21 | coderay (1.1.3) 22 | concurrent-ruby (1.2.2) 23 | daemons (1.4.1) 24 | diff-lcs (1.5.0) 25 | dotenv (2.8.1) 26 | gem_update_checker (0.2.0) 27 | i18n (1.12.0) 28 | concurrent-ruby (~> 1.0) 29 | method_source (1.0.0) 30 | minitest (5.15.0) 31 | os (1.1.4) 32 | pry (0.14.1) 33 | coderay (~> 1.1) 34 | method_source (~> 1.0) 35 | rdoc (6.3.2) 36 | rspec (3.11.0) 37 | rspec-core (~> 3.11.0) 38 | rspec-expectations (~> 3.11.0) 39 | rspec-mocks (~> 3.11.0) 40 | rspec-bash (0.3.0) 41 | sparsify (~> 1.1) 42 | rspec-core (3.11.0) 43 | rspec-support (~> 3.11.0) 44 | rspec-expectations (3.11.0) 45 | diff-lcs (>= 1.2.0, < 2.0) 46 | rspec-support (~> 3.11.0) 47 | rspec-mocks (3.11.1) 48 | diff-lcs (>= 1.2.0, < 2.0) 49 | rspec-support (~> 3.11.0) 50 | rspec-support (3.11.0) 51 | sparsify (1.1.0) 52 | terminal-notifier (2.0.0) 53 | thor (1.2.1) 54 | tzinfo (2.0.6) 55 | concurrent-ruby (~> 1.0) 56 | zeitwerk (2.6.7) 57 | 58 | PLATFORMS 59 | aarch64-linux 60 | x86_64-darwin-17 61 | x86_64-linux 62 | 63 | DEPENDENCIES 64 | activesupport (~> 6.1.7) 65 | docker-sync! 66 | minitest (<= 5.16.3) 67 | os (>= 1.0.0) 68 | pry 69 | rdoc (<= 6.4.0) 70 | rspec 71 | rspec-bash 72 | 73 | BUNDLED WITH 74 | 2.3.21 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/docker-sync.svg)](https://badge.fury.io/rb/docker-sync) [![Build Status](https://www.travis-ci.com/EugenMayer/docker-sync.svg?branch=master)](https://www.travis-ci.com/github/EugenMayer/docker-sync) 2 | 3 | Thank you for all the feedback and support i already received! 4 | Docker-sync has been improved by all of you in huge ways! 5 | 6 | Important links: 7 | 8 | - [Documentation](https://docker-sync.readthedocs.io/en/latest/index.html#) 9 | - [Official homepage docker-sync.io](http://docker-sync.io) 10 | 11 | Docker-sync needs contributions and help 12 | 13 | - Help [here](https://github.com/EugenMayer/docker-sync/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) if you are a coder 14 | - Help [here](https://github.com/EugenMayer/docker-sync/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3A%22help%20wanted%22%20%20label%3A%22documentation%22%20) with the docs no matter what skill set you have. See the [docs/README.md](docs/README.md) on how to work with the docs. 15 | - Otherwise, please consider sponsering me a bear via [Patreon](https://patreon.com/eugenmayer) as an alternative. 16 | 17 | Main Contributors: 18 | 19 | - [ignatiusreza](https://github.com/ignatiusreza) 20 | - [michaelbaudino](https://github.com/michaelbaudino) 21 | - [mickaelperrin](https://github.com/mickaelperrin) 22 | - [masterful](https://github.com/masterful) 23 | - [rwilliams](https://github.com/rwilliams) 24 | 25 | [1.1]: http://i.imgur.com/tXSoThF.png 26 | [1]: http://www.twitter.com/dockersync 27 | -------------------------------------------------------------------------------- /Thorfile: -------------------------------------------------------------------------------- 1 | file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ 2 | file = File.expand_path(File.dirname(file)) 3 | $LOAD_PATH.unshift File.expand_path('./lib', file) 4 | thor = File.expand_path('./tasks', file) 5 | 6 | Dir.glob(File.join(thor, '/**/*.thor')).each { |taskfile| 7 | load taskfile 8 | } 9 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.5 2 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'docker-sync' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # ActiveSupport stuff for readability (specially `1.second` and stuff like that) 10 | require 'active_support' 11 | require 'active_support/core_ext/numeric/time' 12 | require 'active_support/core_ext/integer/time' 13 | require 'active_support/core_ext/object/blank' 14 | 15 | require 'pry' 16 | Pry.start 17 | -------------------------------------------------------------------------------- /bin/docker-sync: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ 3 | file = File.expand_path(File.dirname(file)) 4 | $LOAD_PATH.unshift File.expand_path('../lib', file) 5 | 6 | require 'thor' 7 | require 'docker-sync/update_check' 8 | 9 | thor = File.expand_path('../tasks/sync', file) 10 | Dir.glob(File.join(thor, '/**/*.thor')).each { |taskfile| 11 | load taskfile 12 | } 13 | 14 | Sync.start -------------------------------------------------------------------------------- /bin/docker-sync-daemon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ 3 | file = File.expand_path(File.dirname(file)) 4 | $LOAD_PATH.unshift File.expand_path('../lib', file) 5 | 6 | require 'thor' 7 | require 'docker-sync/update_check' 8 | 9 | thor = File.expand_path('../tasks/daemon', file) 10 | Dir.glob(File.join(thor, '/**/*.thor')).each { |taskfile| 11 | load taskfile 12 | } 13 | thor = File.expand_path('../tasks/sync', file) 14 | Dir.glob(File.join(thor, '/**/*.thor')).each { |taskfile| 15 | load taskfile 16 | } 17 | 18 | Daemon.start -------------------------------------------------------------------------------- /bin/docker-sync-stack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ 3 | file = File.expand_path(File.dirname(file)) 4 | $LOAD_PATH.unshift File.expand_path('../lib', file) 5 | 6 | require 'thor' 7 | require 'docker-sync/update_check' 8 | 9 | thor = File.expand_path('../tasks/stack', file) 10 | Dir.glob(File.join(thor, '/**/*.thor')).each { |taskfile| 11 | # noinspection RubyResolve 12 | load taskfile 13 | } 14 | 15 | Stack.start -------------------------------------------------------------------------------- /deploy_locally.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -f docker-sync-*.gem 3 | gem build docker-sync.gemspec 4 | version=`cat VERSION` 5 | echo| gem uninstall -a --force -q docker-sync 6 | gem install docker-sync-*.gem 7 | rm -f docker-sync-*.gem 8 | 9 | -------------------------------------------------------------------------------- /docker-sync.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'docker-sync' 3 | s.version = File.read('VERSION') 4 | s.summary = 'Docker Sync - Fast and efficient way to sync code to docker-containers' 5 | s.description = 'Sync your code live to docker-containers without losing any performance on OSX' 6 | s.authors = ['Eugen Mayer'] 7 | s.executables = %w[docker-sync docker-sync-stack docker-sync-daemon] 8 | s.email = 'eugen.mayer@kontextwork.de' 9 | s.files = Dir['lib/**/*.rb', 'tasks/**/*.thor', 'Thorfile', 'bin/*', 'VERSION'] 10 | s.license = 'GPL-3.0' 11 | s.homepage = 'https://github.com/EugenMayer/docker_sync' 12 | s.required_ruby_version = '>= 2.5' 13 | 14 | s.add_runtime_dependency 'thor', '~> 1.2', '>= 1.2.1' 15 | s.add_runtime_dependency 'gem_update_checker', '~> 0.2.0', '>= 0.2.0' 16 | s.add_runtime_dependency 'terminal-notifier', '2.0.0' 17 | s.add_runtime_dependency 'dotenv', '~> 2.8', '>= 2.8.1' 18 | s.add_runtime_dependency 'daemons', '~> 1.4', '>= 1.4.1' 19 | s.add_runtime_dependency 'os', '>= 1.0.0' 20 | 21 | s.add_development_dependency 'pry' 22 | s.add_development_dependency 'rdoc', '<= 6.4.0' 23 | end 24 | -------------------------------------------------------------------------------- /docker_sync.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | livehtml: 15 | sphinx-autobuild -E -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O) 16 | 17 | .PHONY: help livehtml Makefile 18 | 19 | # Catch-all target: route all unknown targets to Sphinx using the new 20 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 21 | %: Makefile 22 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 23 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Setting up docs generator locally 2 | 3 | To re-generate the docs on your local machine, simply follow the commands below. 4 | 5 | ## Docker based 6 | Using docker, you will not need any local dependencies 7 | from the docker-sync repo 8 | 9 | ```shell 10 | 11 | docker run -p 8000:8000 -t -v "$(pwd)/docs":/web dldl/sphinx-server 12 | ``` 13 | 14 | Now you can connecto to http://localhost:8000 and browse the docs 15 | 16 | If you change the docs in the source, it will be automatically regenerated and reloaded in the browser 17 | 18 | 19 | ## Without docker 20 | Or of you want to run it locally you will need to install all the dependencies 21 | 22 | ```shell 23 | # Install required dependencies 24 | pip install sphinx sphinx-autobuild sphinx_rtd_theme 25 | ``` 26 | 27 | ```shell 28 | # Spins up the worker that will watch and re-generate the docs when changed 29 | make livehtml 30 | ``` 31 | 32 | Navigate to `http://localhost:8000`. You'll see the latest docs there. 33 | 34 | The pages will re-generate and reload itself when a file is changed. 35 | -------------------------------------------------------------------------------- /docs/_static/native_osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugenMayer/docker-sync/fda21cf1b3bb6f3f94322651eb2375979974c255/docs/_static/native_osx.png -------------------------------------------------------------------------------- /docs/advanced/how-it-works.rst: -------------------------------------------------------------------------------- 1 | ************ 2 | How it works 3 | ************ 4 | 5 | Architecture 6 | ============ 7 | 8 | On the host, a thor based ruby task is started, this starts: 9 | 10 | - Every sync will start an own docker-container with a rsync/unison-daemon watching for connections. 11 | - The data gets pre-synced on sync-start 12 | - A fswatch cli-task gets setup, to run rsync/unison on each file-change in the source-folder you defined 13 | 14 | Done. No magic. But its roadrunner fast! And it has no pre-conditions on your actual stack. 15 | 16 | ---- 17 | 18 | .. _native_osx in depth: 19 | 20 | native_osx in depth 21 | =================== 22 | 23 | Under The Hood 24 | -------------- 25 | 26 | First, take a look at this diagram: 27 | 28 | .. image:: /_static/native_osx.png 29 | :alt: DockerSync native_osx strategy overview 30 | 31 | There are some important keypoints to notice here: 32 | 33 | 1. We use OSXFS to mount your local host folder into the sync-container. 34 | 2. We do not mount this in the app-container directly, since this would lead to `infamously horrible performance`_. 35 | 3. Instead of directly mounting ``/host_sync`` in the app-container we setup a **2-way-sync** inside the sync-container using Unison_. This ensures that the actual READ/WRITE performance on the ``/app_sync`` folder is native-speed fast. 36 | 4. This makes all operations on ``/app_sync`` be asynchronous with ``/host_sync``, since writing and reading on ``/app_sync`` does not rely on any OSXFS operation directly, **but shortly delayed and asynchronous**. 37 | 5. We mount ``/app_sync`` to your app_container - since this happens in hyperkit, it's a **Docker LINUX-based** native mount, thus running at native-speed. 38 | 6. Your application now runs like there was no sync at all. 39 | 40 | .. _infamously horrible performance: https://docs.docker.com/docker-for-mac/osxfs/#performance-issues-solutions-and-roadmap 41 | .. _Unison: http://www.cis.upenn.edu/~bcpierce/unison/ 42 | 43 | FAQ 44 | --- 45 | 46 | Why use OSXFS in the first place (instead of the ``unison`` strategy) to sync from the host to the sync-container 47 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 48 | 49 | There are several reasons, one of the most important being the performance. Since MacOS/OSX has very bad filesystem events support on HFS/APFS, watching the file-system for changes using ``unox`` or ``fswatch`` was causing a heavy CPU load. This CPU load is very significant, even on modern high-end CPUs (like a i7 4770k / 3.5GHz). 50 | 51 | The second issue was dependencies. With native_osx you do not need to install anything on your host OS except the docker-sync gem. So no need to compile unox or install unison manually, deploy with brew and fail along the way - just keeping you system clean. 52 | 53 | Is this strategy absolutely bullet proof? 54 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 55 | 56 | No, it is not. But it has been pretty battle proven already - the main issue is https://github.com/EugenMayer/docker-sync/issues/410 - so sometimes OSXFS just stops triggering FS events in Hyperkit, thus in the sync-container. This leads to an issue with our sync, since the ``unison`` daemon inside the app-sync container relies on those events to sync the changes (it does not have the ability to poll, which would be disastrous performance-wise, anyway). 57 | 58 | Advanced Monitoring for ``native_osx`` 59 | -------------------------------------- 60 | 61 | Background 62 | ^^^^^^^^^^ 63 | 64 | Monit_ is a utility which can be used to monitor the health of the ``unison`` process which runs in the container for the ``native_osx`` strategy. If it detects that ``unison`` is unhealthy, Monit automatically restarts ``unison``. This improves the stability of the ``native_osx`` container in cases where the ``unison`` process is misbehaving but does not necessarily crash. Currently, there is only one check for CPU usage implemented, but in the future more checks may be added, such as memory usage. It is currently turned off by default and can be turned on in the configuration: 65 | 66 | https://github.com/EugenMayer/docker-sync/blob/master/example/docker-sync.yml#L120-L126 67 | 68 | .. _Monit: https://mmonit.com/monit/ 69 | 70 | Monitoring CPU usage 71 | ^^^^^^^^^^^^^^^^^^^^ 72 | 73 | One instance which ``unison`` has been seen to misbehave is when quickly creating and deleting a file while it is processing it. ``unison`` may hang, using a high amount of cpu time: https://github.com/EugenMayer/docker-sync/issues/497. ``monit`` detects this high cpu usage (>50%) and automatically restarts ``unison`` to recover it. By default this happens within 10 seconds, but the tolerance can be configured in case there are normal spikes in cpu usage during successful syncs. 74 | -------------------------------------------------------------------------------- /docs/advanced/scripting.rst: -------------------------------------------------------------------------------- 1 | Scripting 2 | ========= 3 | 4 | We use docker-sync as a library in our own docker-stack startup script. It starts the docker-compose stack using a Ruby gem `EugenMayer/docker-compose`_ all this wrapped into a thor task. So: 5 | 6 | - Start docker-sync 7 | - Start a docker-compose stack based on some arguments like --dev and load the specific docker-compose files for that using `xeger/docker-compose`_ 8 | 9 | docker-sync-stack is actually an example already, just see here: 10 | 11 | 1. You run the sync manager with run : https://github.com/EugenMayer/docker-sync/blob/master/tasks/stack/stack.thor#L37 12 | 2. But you do not call .join_threads after that like her https://github.com/EugenMayer/docker-sync/blob/master/tasks/sync/sync.thor#L36 13 | 3. Then you just continue doing what you want to script, in my case, i start a new blocking task - docker-compose. But you could do anything. 14 | 15 | .. _EugenMayer/docker-compose: https://github.com/EugenMayer/docker-compose 16 | .. _xeger/docker-compose: https://github.com/xeger/docker-compose 17 | 18 | 19 | Simple scripting example 20 | ------------------------ 21 | 22 | .. code-block:: ruby 23 | 24 | require 'docker-sync/sync_manager' 25 | require 'docker-sync/dependencies' 26 | require 'docker-sync/config/project_config' 27 | 28 | # load the project config 29 | config = DockerSync::ProjectConfig.new(config_path: nil) 30 | DockerSync::Dependencies.ensure_all!(config) 31 | # now start the sync 32 | @sync_manager = Docker_sync::SyncManager.new(:config_path => config_path) 33 | @sync_manager.run() # do not call .join_threads now 34 | 35 | #### your stuff here 36 | @sync_manager.watch_stop() 37 | system('my-bash-script.sh') 38 | some_ruby_logic() 39 | system('other-tasks.sh') 40 | @sync_manager.sync() 41 | @sync_manager.watch_start() 42 | ### debootsrapping 43 | begin 44 | @sync_manager.join_threads 45 | rescue SystemExit, Interrupt 46 | say_status 'shutdown', 'Shutting down...', :blue 47 | 48 | @sync_manager.stop 49 | rescue Exception => e 50 | 51 | puts "EXCEPTION: #{e.inspect}" 52 | puts "MESSAGE: #{e.message}" 53 | 54 | end 55 | -------------------------------------------------------------------------------- /docs/advanced/sync-strategies.rst: -------------------------------------------------------------------------------- 1 | Sync strategies 2 | =============== 3 | 4 | The sync strategies depend on the OS, so not all strategies are available on all operating system 5 | 6 | - OSX: native_osx, unison, rsync 7 | - Windows: unison 8 | - Linux: native_linux, unison 9 | 10 | ---- 11 | 12 | .. _strategies-native-osx: 13 | 14 | native_osx (OSX) 15 | ---------------- 16 | 17 | .. image:: /_static/native_osx.png 18 | :alt: DockerSync native_osx strategy overview 19 | 20 | For advanced understanding, please read :ref:`native_osx in depth`. 21 | 22 | Native-OSX is a combination of two concepts, `OSXFS only`_ and Unison together. We use OSX to sync the file-system into a sync-container to /host_sync. In that sync container we sync from /host_sync to /app_sync using Unison. /app_sync is exposed as a named volume mount and consumed in the app. You ask yourself, why? Its fairly simple. 23 | 24 | By having this extra layer of unison on linux, we detach the actual write/read performance of your application from the actual OSXFS performance - running at native speed. Still, we using OSXFS, a about up to 1 second delayed, to synchronize changes both ways. So we have a 2-way sync. 25 | 26 | What is different to plain Unison you might ask. The first big issue with Unison is, how bad it performs under OSX due to the bad FS-Events of OSX, implemented in macfsevents and alikes. It uses a lot of CPU for watching files, it can lose some events and miss a sync - but also, it adds extra dependencies on the OSX hosts. 27 | 28 | All that is eliminated by native_osx - we use Unison in the Linux container, where it performs great due to inotify-event based watchers. 29 | 30 | Pros 31 | - Far more reliable due to a low-level implementation of the sync using `OSXFS only`_ 32 | - Uses far less CPU to sync files from the host to the sync container - will handle a lot more files 33 | - No daemon to run, control, restart and sync on the OSX host. Makes sleep/hibernate/reboot much more robust 34 | - No dependencies on the OSX-Host at all 35 | - A lot easier installation since we just need ``gem install docker-sync`` and that on runs under system ruby. Anything else is in containers 36 | - It performs at native speed 37 | - It will be much easier to support Windows this way 38 | 39 | Cons 40 | - Initial start can take longer as on unison, since the first sync is more expensive. But this is one time only 41 | - It works under Docker for Mac only - missing file system events under vbox/fusion. See `native_osx does not work with docker-machine vbox / fusion`_ 42 | 43 | .. _OSXFS only: https://github.com/EugenMayer/docker-sync/issues/346 44 | .. _native_osx does not work with docker-machine vbox / fusion: https://github.com/EugenMayer/docker-sync/issues/346 45 | 46 | ---- 47 | 48 | unison (OSX/Windows/Linux) 49 | -------------------------- 50 | 51 | This strategy has the biggest drive to become the new default player out of the current technologies. It seems to work very well with huge codebases too. It generally is build to handle 2 way sync, so syncs back changes from the container to the host. 52 | 53 | Pros 54 | - Offers 2 way sync (please see unison-dualside why this is misleading here) 55 | - Still very effective and works for huge projects 56 | - Native speed in for the application 57 | 58 | Cons 59 | - Can be unreliable with huge file counts (> 30.000) and bad hardware (events gets stuck) 60 | - The daemon on OSX needs extra care for sleep/hibernate. 61 | - Extra dependencies we need on OSX, in need to install unison and unox natively - brew dependencies 62 | 63 | **Initial startup delays with unison** 64 | 65 | On initial start, Unison sync needs to build a catalog of the files in the synced folder before sync begins. As a result, when syncing folders with large numbers of relatively large files (for example, 40k+ files occupying 4G of space) using unison, you may see a significant delay (even 20+ minutes) between the initial ``ok Starting unison`` message and the ``ok Synced`` message. This is not a bug. Performance in these situations can be improved by moving directories with a large number of files into a separate ``rsync`` strategy sync volume, and using unison only on directories where two-way sync is necessary. 66 | 67 | ---- 68 | 69 | rsync (OSX) 70 | ----------- 71 | 72 | This strategy is probably the simplest one and probably the most robust one for now, while it has some limitations. rsync-syncs are known to be pretty fast, also working very efficient on huge codebases - no need to be scared of 20k files. rsync will easily rsync the changes ( diff ) very fast. 73 | 74 | Pros 75 | - Fast re-syncing huge codebases - sync diffs (faster then unison? proof?) 76 | - Well tested and known to be robust 77 | - Best handling of user-permission handling ( mapped into proper users in the app container ) 78 | 79 | Cons 80 | - Implements a one way sync only, means only changes of your codebase on the host are transferred to the app-container. Changes of the app-container are not synced back at all 81 | - Deleting files on the host does yet not delete them on the container, since we do not use --delete, see `#347`_ 82 | 83 | Example: On the docker-sync-boilerplate_ 84 | 85 | .. _#347: https://github.com/EugenMayer/docker-sync/issues/37 86 | .. _docker-sync-boilerplate: https://github.com/EugenMayer/docker-sync-boilerplate/tree/master/rsync 87 | 88 | ---- 89 | 90 | native_linux 91 | ------------ 92 | 93 | Native linux is actually no real implementation, it just wraps docker-sync around the plain native docker mounts which do work perfectly on linux. So there are no sync-containers, no strategies or what so ever. This strategy is mainly used just to have the whole team use docker-sync, even on linux, to have the same interface. If you use default sync_strategy in the docker-sync.yml, under Linux, native_linux is picked automatically 94 | 95 | ---- 96 | 97 | Sync Flags or Options 98 | --------------------- 99 | 100 | You find the available options for each strategy in :doc:`../getting-started/configuration`. 101 | -------------------------------------------------------------------------------- /docs/advanced/tips-and-tricks.rst: -------------------------------------------------------------------------------- 1 | *************** 2 | Tips and tricks 3 | *************** 4 | 5 | HTTP Proxy and DNS 6 | ================== 7 | 8 | The HTTP Proxy and DNS used in dinghy_ is available as a standalone project dinghy-http-proxy_. The proxy is based on jwilder's excellent 9 | nginx-proxy_ project, with modifications to make it more suitable for local development work. A DNS resolver is also added. By default it will resolve all ``*.docker`` domains to the Docker VM, but this can be changed. 10 | 11 | .. _dinghy: https://github.com/codekitchen/dinghy 12 | .. _dinghy-http-proxy: https://github.com/codekitchen/dinghy-http-proxy 13 | .. _nginx-proxy: https://github.com/jwilder/nginx-proxy 14 | 15 | SSH-Agent Forwarding 16 | ==================== 17 | 18 | If you need to access some private git repos or ssh servers, it could be useful to use have a ssh-agent accessible from your containers. `whilp/ssh-agent`_ helps you to do so easily. 19 | 20 | .. _whilp/ssh-agent: https://github.com/whilp/ssh-agent 21 | 22 | Running composer or other tools like if they were on the host 23 | ============================================================= 24 | 25 | If you run composer and other tools directly in containers, you could use a combination of the autoenv_ zsh plugin and a simple wrapper script to run it easily directly from the host. In your project folder, create a ``.autoenv.zsh`` file with the name of your container: 26 | 27 | .. code-block:: shell 28 | 29 | autostash COMPOSER_CONTAINER='project_fpm_1' 30 | 31 | then create a simple function in your ``.zshrc``: 32 | 33 | 34 | .. code-block:: shell 35 | 36 | composer () { 37 | if [ ! -z $COMPOSER_CONTAINER ]; then 38 | docker exec -it ${COMPOSER_CONTAINER} /usr/local/bin/composer --working-dir=/src -vv "$@" 39 | else 40 | /usr/local/bin/composer "$@" 41 | fi 42 | } 43 | 44 | .. _autoenv: https://github.com/Tarrasch/zsh-autoenv 45 | 46 | Ignore files in your IDE 47 | ======================== 48 | 49 | It's a good idea to add the temporary sync files to your IDE's ignore list to prevent your IDE from indexing them all the time or showing up in search results. In case of unison and PHPStorm for example just go to Preferences -> File Types -> Ignore files and folders and add ``.unison`` to the pattern. 50 | 51 | Don't sync everything 52 | ===================== 53 | 54 | You should only sync files that you really need on both the host and client side. You will see that the sync performance will improve drastically when you ignore unnecessary files. How and which files to ignore depends on your syncing strategy (rsync/unison/...) and your project. 55 | 56 | Example for a PHP Symfony project using unison: 57 | 58 | .. code-block:: yaml 59 | 60 | # docker-sync.yml 61 | syncs: 62 | appcode-unison-sync: 63 | ... 64 | sync_args: 65 | - "-ignore='Path .idea'" # no need to send PHPStorm config to container 66 | - "-ignore='Path .git'" # ignore the main .git repo 67 | - "-ignore='BelowPath .git'" # also ignore .git repos in subfolders such as in composer vendor dirs 68 | - "-ignore='Path var/cache/*'" # don't share the cache 69 | - "-ignore='Path var/sessions/*'" # we don't need the sessions locally 70 | - "-ignore='Path node_modules/*'" # remove this if you need code completion 71 | # - "-ignore='Path vendor/*'" # we could ignore the composer vendor folder, but then you won't have code completion in your IDE 72 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | import os 10 | 11 | # -- Path setup -------------------------------------------------------------- 12 | 13 | # If extensions (or modules to document with autodoc) are in another directory, 14 | # add these directories to sys.path here. If the directory is relative to the 15 | # documentation root, use os.path.abspath to make it absolute, like shown here. 16 | # 17 | # import os 18 | # import sys 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | # import sphinx_rtd_theme 21 | 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'docker-sync' 26 | copyright = '2019, Eugen Mayer' 27 | author = 'Eugen Mayer' 28 | 29 | # The short X.Y version 30 | version = '0.5.11' 31 | # The full version, including alpha/beta/rc tags 32 | release = '0.5.11' 33 | 34 | 35 | # -- General configuration --------------------------------------------------- 36 | 37 | # If your documentation needs a minimal Sphinx version, state it here. 38 | # 39 | # needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = [ 45 | 'sphinx.ext.doctest', 46 | 'sphinx.ext.todo', 47 | "sphinx_rtd_theme", 48 | ] 49 | 50 | html_theme = "sphinx_rtd_theme" 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix(es) of source filenames. 56 | # You can specify multiple suffix as a list of string: 57 | # 58 | # source_suffix = ['.rst', '.md'] 59 | source_suffix = '.rst' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This pattern also affects html_static_path and html_extra_path. 74 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'sphinx' 78 | 79 | 80 | # -- Options for HTML output ------------------------------------------------- 81 | 82 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 83 | if not on_rtd: # only import and set the theme if we're building docs locally 84 | import sphinx_rtd_theme 85 | html_theme = 'sphinx_rtd_theme' 86 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 87 | 88 | # Theme options are theme-specific and customize the look and feel of a theme 89 | # further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | html_theme_options = { 93 | 'collapse_navigation': False 94 | } 95 | 96 | # Add any paths that contain custom static files (such as style sheets) here, 97 | # relative to this directory. They are copied after the builtin static files, 98 | # so a file named "default.css" will overwrite the builtin "default.css". 99 | html_static_path = ['_static'] 100 | 101 | # Custom sidebar templates, must be a dictionary that maps document names 102 | # to template names. 103 | # 104 | # The default sidebars (for documents that don't match any pattern) are 105 | # defined by theme itself. Builtin themes are using these templates by 106 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 107 | # 'searchbox.html']``. 108 | # 109 | # html_sidebars = {} 110 | 111 | 112 | # -- Options for HTMLHelp output --------------------------------------------- 113 | 114 | # Output file base name for HTML help builder. 115 | htmlhelp_basename = 'docker-syncdoc' 116 | 117 | 118 | # -- Options for LaTeX output ------------------------------------------------ 119 | 120 | latex_elements = { 121 | # The paper size ('letterpaper' or 'a4paper'). 122 | # 123 | # 'papersize': 'letterpaper', 124 | 125 | # The font size ('10pt', '11pt' or '12pt'). 126 | # 127 | # 'pointsize': '10pt', 128 | 129 | # Additional stuff for the LaTeX preamble. 130 | # 131 | # 'preamble': '', 132 | 133 | # Latex figure (float) alignment 134 | # 135 | # 'figure_align': 'htbp', 136 | } 137 | 138 | # Grouping the document tree into LaTeX files. List of tuples 139 | # (source start file, target name, title, 140 | # author, documentclass [howto, manual, or own class]). 141 | latex_documents = [ 142 | (master_doc, 'docker-sync.tex', 'docker-sync Documentation', 143 | 'EugenMayer', 'manual'), 144 | ] 145 | 146 | 147 | # -- Options for manual page output ------------------------------------------ 148 | 149 | # One entry per manual page. List of tuples 150 | # (source start file, name, description, authors, manual section). 151 | man_pages = [ 152 | (master_doc, 'docker-sync', 'docker-sync Documentation', 153 | [author], 1) 154 | ] 155 | 156 | 157 | # -- Options for Texinfo output ---------------------------------------------- 158 | 159 | # Grouping the document tree into Texinfo files. List of tuples 160 | # (source start file, target name, title, author, 161 | # dir menu entry, description, category) 162 | texinfo_documents = [ 163 | (master_doc, 'docker-sync', 'docker-sync Documentation', 164 | author, 'docker-sync', 'One line description of project.', 165 | 'Miscellaneous'), 166 | ] 167 | 168 | 169 | # -- Options for Epub output ------------------------------------------------- 170 | 171 | # Bibliographic Dublin Core info. 172 | epub_title = project 173 | 174 | # The unique identifier of the text. This can be a ISBN number 175 | # or the project homepage. 176 | # 177 | # epub_identifier = '' 178 | 179 | # A unique identification for the text. 180 | # 181 | # epub_uid = '' 182 | 183 | # A list of files that should not be packed into the epub file. 184 | epub_exclude_files = ['search.html'] 185 | 186 | 187 | # -- Extension configuration ------------------------------------------------- 188 | 189 | # -- Options for todo extension ---------------------------------------------- 190 | 191 | # If true, `todo` and `todoList` produce output, else they produce nothing. 192 | todo_include_todos = True 193 | 194 | html_context = { 195 | "display_github": True, # Integrate GitHub 196 | "github_user": "EugenMayer", # Username 197 | "github_repo": "docker-sync", # Repo name 198 | "github_version": "main", # Version 199 | "conf_py_path": "/docs/", # Path in the checkout to the docs root 200 | } 201 | -------------------------------------------------------------------------------- /docs/getting-started/commands.rst: -------------------------------------------------------------------------------- 1 | ******** 2 | Commands 3 | ******** 4 | 5 | Sync commands (``docker-sync``) 6 | =============================== 7 | 8 | Generally you can just list all the help in the cli by: 9 | 10 | .. code-block:: shell 11 | 12 | docker-sync help 13 | 14 | Start 15 | ----- 16 | 17 | .. code-block:: shell 18 | 19 | docker-sync start 20 | 21 | .. tip:: 22 | 23 | See :ref:`sync-stack-commands` on how ``docker-sync-stack start`` works to start sync / compose at the same time. 24 | 25 | This creates and starts the sync containers, watchers and the sync itself. It blocks your shell and you should leave it running in the background. When you are done, just press ``CTRL-C`` and the containers will be stopped ( not removed ). 26 | 27 | Running start the second time will be a lot faster, since containers and volumes are reused. 28 | 29 | .. tip:: 30 | 31 | You can use ``-n `` to only start one of your configured sync-endpoints. 32 | 33 | Restart 34 | ------- 35 | 36 | .. code-block:: shell 37 | 38 | docker-sync restart 39 | 40 | This restarts docker-sync daemon, so this is the same as ``docker-sync stop && docker-sync start``. You can use the same options as ``docker-sync start`` command. 41 | 42 | Sync 43 | ---- 44 | 45 | .. code-block:: shell 46 | 47 | docker-sync sync 48 | 49 | This forces docker-sync to sync the host files to the sync-containers. You must have the containers running already (``docker-sync start``). Use this as a manual trigger, if either the change-watcher failed or you try something special / an integration 50 | 51 | .. tip:: 52 | 53 | You can use ``-n `` to only sync one of your configured sync-endpoints. 54 | 55 | List 56 | ---- 57 | 58 | .. code-block:: shell 59 | 60 | docker-sync list 61 | 62 | List all available/configured sync-endpoints configured for the current project. 63 | 64 | Clean 65 | ----- 66 | 67 | After you are done and want to free up space or switch to a different project, you might want to release the sync containers and volumes by 68 | 69 | .. code-block:: shell 70 | 71 | docker-sync clean 72 | 73 | This will not delete anything on your host source code folders or similar, it just removes the container for sync and its volumes. It does not touch your application stack. 74 | 75 | ---- 76 | 77 | .. _sync-stack-commands: 78 | 79 | Sync stack commands (``docker-sync-stack``) 80 | =========================================== 81 | 82 | With docker-sync there comes docker-sync-stack ( from 0.0.10 ). Using this, you can start the sync service and docker compose with one single command. This is based on the gem docker-compose_. 83 | 84 | Start 85 | ----- 86 | 87 | .. code-block:: shell 88 | 89 | docker-sync-stack start 90 | 91 | This will first start the sync service like ``docker-sync start`` and then start your compose stack like ``docker-compose up``. 92 | 93 | You do not need to run ``docker-sync start`` beforehand! 94 | 95 | **This is very convenient so you only need one shell, one command to start working and CTRL-C to stop.** 96 | 97 | Clean 98 | ----- 99 | 100 | .. code-block:: shell 101 | 102 | docker-sync-stack clean 103 | 104 | This cleans the sync-service like ``docker-sync clean`` and also removed the application stack like ``docker-compose down``. 105 | 106 | .. _docker-compose: https://github.com/xeger/docker-compose 107 | 108 | ---- 109 | 110 | .. _daemon-mode: 111 | 112 | Daemon mode 113 | =========== 114 | 115 | Docker-sync in daemon mode 116 | -------------------------- 117 | 118 | Beginning with version **0.4.0** Daemon mode is now the default, just use ``docker-sync start``. ``docker-sync-daemon`` is deprecated. 119 | 120 | ----- 121 | 122 | Beginning with version **0.2.0**, docker-sync has the ability to run in a daemonized (background) mode. 123 | 124 | In general you now run `docker-sync-daemon` to start in daemonized mode, type ``docker-sync-daemon `` to see all options 125 | 126 | Start 127 | ----- 128 | 129 | The `docker-sync-daemon start` command has the following options to help configure daemon mode: 130 | 131 | - ``--app_name`` (``--name``), The name to use in the filename for the ``pid`` and ``output`` files (default: 'daemon') 132 | - ``--dir``, The directory to place the ``pid`` and ``output`` files (default: './.docker-sync') 133 | - ``--logd``, Whether or not to log the output (default: true) 134 | 135 | Stop 136 | ---- 137 | 138 | The ``docker-sync-daemon stop`` command is available to stop the background process. It also takes the ``--app_name`` and ``--dir`` arguments. 139 | 140 | Log 141 | --- 142 | 143 | The ``docker-sync-daemon logs`` command is a handy shortcut to tail the logs from the daemonized process, in addition to the ``--app_name`` and ``--dir`` from above, it takes the following arguments: 144 | 145 | - ``--lines``, Specify the maximum number of lines to print from the current end of the log file (defaults to 100) 146 | - ``--follow`` (``-f``), Whether or not to continue following the log (press ctrl+c to stop following) 147 | 148 | Examples 149 | -------- 150 | 151 | **Instead of docker-sync-stack start** 152 | 153 | The way ``docker-sync-stack start`` used to operate was to begin to sync the container(s) specified in the ``docker-sync.yml`` file, and then begin a ``docker-compose up``. The simplest way to replace this command is to use: 154 | 155 | .. code-block:: shell 156 | 157 | docker-sync-daemon start 158 | docker-compose up 159 | 160 | This will start your sync in the background, and then start all services defined in your docker-compose file in the foreground. This means that your sync continues in the background, even if you exit your ``docker-compose`` session(s). You can then stop that background sync with: 161 | 162 | 163 | .. code-block:: shell 164 | 165 | docker-sync-daemon stop 166 | 167 | This will show the logs for the daemon started above 168 | 169 | .. code-block:: shell 170 | 171 | docker-sync-daemon logs 172 | 173 | **Running commands before starting the docker-compose services** 174 | 175 | By having the sync run in the background, you can then use a single shell session to ensure that the sync is running, and then run a few commands before starting all your services. You may wish to do this if you would like to use volumes to speed up rebuilds for node modules or gem bundles - as volumes are not available while building the image, but are when building the container. 176 | 177 | .. code-block:: shell 178 | 179 | docker-sync-daemon start 180 | docker-compose run --rm $service yarn install 181 | docker-compose up -d 182 | 183 | This will ensure that your sync containers are up and available so that commands utilizing the docker-compose file don't fail for not finding those containers. It will then run all services in the background. 184 | 185 | Notes 186 | ----- 187 | 188 | **New directory** 189 | 190 | This will now create a ``.docker-sync`` directory alongside wherever you invoke the command (if you're asking it to run in the background). You will likely want to add this directory to your ``.gitignore`` file (or equivalent). You can, of course, use the ``--dir`` option to specify an alternate directory to save these files, but be sure to pass the same argument to ``stop``, and to use it consistently, or you may end up with multiple sync's running in the background... 191 | 192 | **Invoking with the --config option** 193 | 194 | I imagine most users will be invoking ``docker-sync`` without specifying an alternate path to the config file, but it's worth mentioning that if that's your current setup, you should also consider using the ``app_name`` option or the ``dir`` option to ensure that your ``pid`` file won't conflict with other invocations of docker-sync - otherwise you'll get a message saying that it's already running. 195 | -------------------------------------------------------------------------------- /docs/getting-started/upgrade.rst: -------------------------------------------------------------------------------- 1 | Upgrade 2 | ======= 3 | 4 | 0.4.2-0.4.5 5 | ----------- 6 | 7 | Nothing special, just be sure to pull the newest docker-images for your strategy and do not leave any older version behind 8 | 9 | ---- 10 | 11 | 0.4.1 12 | ----- 13 | - ``:nocopy`` needs to be added to all named-volume mounts, see :ref:`why-nocopy-important` 14 | 15 | - if you want to use native_osx with docker-machine ( toolbox ) + virtualbox, it will not work https://github.com/EugenMayer/docker-sync/issues/346 16 | 17 | The ``:nocopy`` issue has been there for a while, but nobody really recognized it. 18 | 19 | ---- 20 | 21 | 0.4.0 22 | ----- 23 | 24 | .. attention:: 25 | 26 | Ensure you run docker-sync clean after the upgrade. Do not reuse the old containers! 27 | 28 | **The default strategy is now native_osx** 29 | 30 | Read more at :ref:`strategies-native-osx`. 31 | 32 | **Background by default** 33 | 34 | ``docker-sync start`` does now background by default, use ``--foreground`` to run in foreground. 35 | 36 | **sync_user now removed** 37 | 38 | Being deprecated in 0.2, it's now throwing an error if still present 39 | 40 | **docker-sync-daemon is now deprecated** 41 | 42 | It's deprecated and will be removed in 0.5 43 | 44 | ---- 45 | 46 | 0.3.0 47 | ----- 48 | 49 | **Reinstallation of unox** 50 | 51 | Due to a lot of issues and inconvenience with the installation of unox and the lack of versioning of unox, i took the step to create a homebrew formula myself, while working with the unox author hand in hand. This way we can ease up the installation and also be able to avoid issues as https://github.com/EugenMayer/docker-sync/issues/296 The installer will take care of everything for you in this regard 52 | 53 | **Scaffolding usages needs to be migrated** 54 | 55 | If you scaffolded / scripted with docker-sync using it as a ruby lib, you will now need to change your implementation due to the changes to preconditions and config. Important new/replacing calls are. Please see the updated example at :doc:`../advanced/scripting` for how to load the project config, how to get its path and how to call the preconditions 56 | 57 | **Dest has been removed** 58 | 59 | After making dest deprecated in the 0.2 release, since we introduce named volume mounts, it is now removed. Just remove it, the destination is set in the docker-compose-dev.yml file like this 60 | 61 | .. code-block:: shell 62 | 63 | version: "2" 64 | services: 65 | someapp: 66 | volumes: 67 | - fullexample-sync:/var/www:rw # will be mounted on /var/www 68 | 69 | So here, the destination will be ``/var/www`` 70 | 71 | ---- 72 | 73 | 0.2.0+ 74 | ------ 75 | 76 | **Versioning of docker-sync.yml** 77 | 78 | From 0.2.0 you need to add a new setting to your docker-sync.yml ``version: "2"`` - this describes the project version you are using. This is needed so later we can easily detect old/incompatible configuration files and warn you to migrate those. This is now mandatory. See this change_ on the boilerplates / examples, which may explain it even better. 79 | 80 | **Unison exclude syntax is Name by default now - migrate your entries** 81 | 82 | Prior to 0.2.0 the exclude default syntax of the unison strategy was "Path" - since we decided that this is counter-intuitive in most cases, we have changed the default to ``Name`` - please see the `unison documentation for more`_ - mostly you would have expected the ``Name`` behavior anyway, so you might want to stick with it. TLTR: ``Path`` math matches the exact path ( not sub-directories ) while name just matches string on the path - no matter which "nesting level". You could go back to ``Path`` by setting sync_exclude_type_ to 'Path'. 83 | 84 | See this issue: `Make Name the default exclude_type in 0.2.0`_. 85 | 86 | **rsync trailing slash changes** 87 | 88 | Prior to 0.2.0 the trailing slash was automatically added - but now you have to do this explicitly If you define an rsync sync, you most probably want to sync the inner folder into you destination, without creating the parent folder / syncing it. This trailing slash ``./your-code/`` ensures exactly that, so ``your-code`` will not be created on your destination, but anything inside it will be synced. 89 | 90 | **Default sync is now unison (from rsync to unison)** 91 | 92 | If you did not provide the sync_strategy setting prior 0.2.0 - rsync was used. Starting with 0.2.0 unison(dual sided) is the new default, so a 2 way sync. Beside its just being better, faster after the initial sync and also offers 2-way sync, it has a new Exclude-syntax. With 0.2.0 the ``Name`` exclude syntax is used, ensure you adjust your rsync ones to fit those. 93 | 94 | See this issue: `Migration Guide from rsync to unison as default`_. 95 | 96 | **volumes_from: container: syntax is no longer used** 97 | 98 | The ``volumes_from: container:app-sync:rw`` syntax is no longer used as a volume mount for the sync container, but rather ``volumes: app-sync:/var/www:rw`` 99 | 100 | See this issue: `Rework the way we mount the volume`_. 101 | 102 | **--prefer is now built in - remove it from sync_args** 103 | 104 | If you have used sync_args for unison and defined ``--prefer``, please consider removing it. Without doing anything, docker-sync will now use ``--prefer --copyonconflict`` and also help you keep the src dynamic (depending on the developer). 105 | 106 | **The option sync_user no longer exists** 107 | 108 | ``sync_user`` has been removed, since it does not add any useful stuff, but spreads a lot of confusion. Please use ``sync_userid`` solely to define the user-mapping, no need to manually set the ``sync_user`` anymore. 109 | 110 | **Remove the old unison:unox image** 111 | 112 | Since the name was misleading anyway, please remove the old unison image: ``docker image rm eugenmayer/unison:unox``. 113 | 114 | **The rsync / unison images have been remade and aligned** 115 | 116 | To share more code and features between the rsync / unison images, we aligned those images to share the same codebase, thus they have been renamed. The ENV variables have changed and some things you should not even notice, since it is all handled by ``docker-sync`` - all you need to know is, you need to pull the new versions if you have disabled the auto-pull (which you should not). 117 | 118 | .. _change: https://github.com/EugenMayer/docker-sync-boilerplate/commit/9d2cd625282f968161e3ecf4ed85b5b52dbd8cbd 119 | .. _unison documentation for more: http://www.cis.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html#ignore 120 | .. _sync_exclude_type: https://github.com/EugenMayer/docker-sync/blob/master/example/docker-sync.yml#L56 121 | .. _Make Name the default exclude_type in 0.2.0: https://github.com/EugenMayer/docker-sync/issues/133 122 | .. _Rework the way we mount the volume: https://github.com/EugenMayer/docker-sync/issues/116 123 | .. _Migration Guide from rsync to unison as default: https://github.com/EugenMayer/docker-sync/issues/115 124 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | docker-sync 2 | =========== 3 | 4 | Run your application at full speed while syncing your code for development, finally empowering you to utilize docker for development under OSX/Windows/Linux* 5 | 6 | Introduction 7 | ============ 8 | 9 | Developing with docker_ under OSX/ Windows is a huge pain, since sharing your code into containers will slow down the code-execution about 60 times (depends on the solution). Testing and working with a lot of the alternatives made us pick the best of those for each platform, and combine this in one single tool: ``docker-sync``. 10 | 11 | - For OSX, see :ref:`installation-osx`. 12 | - For Windows, see :ref:`installation-windows`. 13 | - For Linux, see :ref:`installation-linux`. 14 | - See the list of alternatives at :doc:`../miscellaneous/alternatives` 15 | 16 | .. _docker: https://www.docker.com/ 17 | 18 | Features 19 | -------- 20 | 21 | - Support for OSX, Windows, Linux and FreeBSD 22 | - Runs on Docker for Mac, Docker for Windows and Docker Toolbox 23 | - Uses either native_osx, unison or rsync as possible strategies. The container performance is not influenced at all, see :doc:`../miscellaneous/performance` 24 | - Very efficient due to the :ref:`native_osx in depth` 25 | - Without any dependencies on OSX when using (native_osx) 26 | - Backgrounds as a daemon 27 | - Supports multiple sync-end points and multiple projects at the same time 28 | - Supports user-remapping on sync to avoid permission problems on the container 29 | - Can be used with your docker-compose way or the integrated docker-compose way to start the stack and sync at the same time with one command 30 | - Using overlays to keep your production docker-compose.yml untouched and portable. See :ref:`docker-compose-yml`. 31 | - Supports Linux* to use the same toolchain across all platforms, but maps on a native mount in linux (no sync) 32 | - Besides performance being the first priority for docker-sync, the second is, not forcing you into using a specific docker solution. Use docker-for-mac, docker toolbox, VirtualBox, VMware Fusion or Parallels, xhyve or whatever! 33 | 34 | .. toctree:: 35 | :hidden: 36 | :caption: Introduction 37 | 38 | Introduction 39 | 40 | .. toctree:: 41 | :hidden: 42 | :caption: Getting Started 43 | :maxdepth: 2 44 | 45 | getting-started/installation 46 | getting-started/configuration 47 | getting-started/commands 48 | getting-started/upgrade 49 | 50 | .. toctree:: 51 | :hidden: 52 | :caption: Advanced 53 | :maxdepth: 2 54 | 55 | advanced/sync-strategies 56 | advanced/scripting 57 | advanced/how-it-works 58 | advanced/tips-and-tricks 59 | 60 | .. toctree:: 61 | :hidden: 62 | :caption: Troubleshooting 63 | :maxdepth: 2 64 | 65 | troubleshooting/sync-stopping 66 | troubleshooting/common-issues 67 | troubleshooting/native-osx-troubleshooting 68 | 69 | .. toctree:: 70 | :hidden: 71 | :caption: Miscellaneous 72 | :maxdepth: 2 73 | 74 | miscellaneous/development-and-testing 75 | miscellaneous/performance 76 | miscellaneous/alternatives 77 | miscellaneous/changelog 78 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/miscellaneous/alternatives.rst: -------------------------------------------------------------------------------- 1 | Alternatives 2 | ============ 3 | 4 | This is a list of alternatives grouped by technology. Feel free to add the missing ones. 5 | 6 | Docker native 7 | ------------- 8 | 9 | Transparent, consistent, dual-sided (host -> container, container -> host) synchronization. Performance is here a trade-off for consistency. Can be 2-100 times slower than nfs and even more as compared with rsync. 10 | 11 | - docker-toolbox_: virtualBox / fusion VM (horribly slow) 12 | - `docker for mac`_: uses osxfs_. See osxfs-caching_ for optimization ideas. As of October 2017, they aren't proven to be effective yet. 13 | 14 | .. _docker-toolbox: https://www.docker.com/products/docker-toolbox 15 | .. _docker for mac: https://docs.docker.com/docker-for-mac/ 16 | .. _osxfs: https://docs.docker.com/docker-for-mac/osxfs/ 17 | .. _osxfs-caching: https://docs.docker.com/docker-for-mac/osxfs-caching/ 18 | 19 | OSXFS + unison 20 | -------------- 21 | 22 | Dedicated container mounts a local directory via osxfs and runs Unison to synchronize this mount with a Docker volume. 23 | - docker-magic-sync_ 24 | - docker-sync_ implements osxfs+unison-based sync when 'native_osx' is used as a strategy, being the default since 0.4.x. We use a special technique to achieve better performance, we sync with osxfs but the container still runs at native speed, let's call it decoupled sync. 25 | 26 | .. _docker-magic-sync: https://github.com/mickaelperrin/docker-magic-sync 27 | 28 | Unison 29 | ------ 30 | 31 | Unison runs both on the host and in a Docker container and synchronizes the macOS directory with a Docker container with Unison. **osxfs + unison** is a preferred alternative, because it's simpler and more reliable (bad FSEvents performance). 32 | 33 | - docker-sync_ - unison can be used with docker-sync as well as a strategy, just set `sync_strategy: unison` 34 | - Hodor_ (should be as fast as rsync?) 35 | 36 | .. tip:: 37 | 38 | You can choose to use Unison with docker-sync by adding sync_strategy: 'unison' to a sync-point too 39 | 40 | .. _Hodor: https://github.com/gansbrest/hodor 41 | 42 | Rsync 43 | ----- 44 | 45 | Performance: Exactly the performance you would have without shares. Downside: **one-way sync**. 46 | 47 | - docker-sync_ - rsync can be used with docker-sync as well as a strategy, just set `sync_strategy: rsync` 48 | - docker-dev-osx_ (rsync, vbox only) - Hint: If you are happy with docker-machine and virtual box, this is a pretty solid alternative. It has been there for ages and is most probably pretty advanced. For me, it was no choice, since neither i want to stick to VBox nor it has support for docker-for-mac 49 | 50 | .. _docker-dev-osx: https://github.com/brikis98/docker-osx-dev 51 | 52 | NFS 53 | --- 54 | Performance: In general, at least 3 times slower than **rsync**, often even more. 55 | 56 | - Dinghy_ (docker-machine only, no docker for mac) 57 | - DLite_ (docker-machine only, no docker for mac) 58 | - Dusty_ (docker-machine only, no docker for mac) 59 | 60 | .. _Dinghy: https://github.com/codekitchen/dinghy 61 | .. _DLite: https://github.com/nlf/dlite 62 | .. _Dusty: http://dusty.gc.com/ 63 | 64 | .. _docker-sync: https://docker-sync.io 65 | -------------------------------------------------------------------------------- /docs/miscellaneous/development-and-testing.rst: -------------------------------------------------------------------------------- 1 | ********************* 2 | Development & testing 3 | ********************* 4 | 5 | Development 6 | =========== 7 | 8 | You do not really need a lot to start developing. 9 | 10 | - A local Ruby >= 2.0 11 | 12 | .. code-block:: shell 13 | 14 | git clone https://github.com/eugenmayer/docker-sync 15 | cd docker-sync 16 | bundle install 17 | gem uninstall -a docker-sync 18 | 19 | **Important**: To properly develop, uninstall docker-sync as a gem so it is not used during the runs: 20 | ``gem uninstall -a docker-sync``. 21 | 22 | Now you can: 23 | 24 | .. code-block:: shell 25 | 26 | cd example 27 | thor sync:start 28 | 29 | or 30 | 31 | .. code-block:: shell 32 | 33 | thor stack:start 34 | 35 | So you see, what is separated in to binaries in production ``docker-sync`` and ``docker-sync-stack`` is bundled under one namespace here, but prefixed. 36 | 37 | General layout 38 | -------------- 39 | 40 | Check libs folder. 41 | 42 | - SyncManager_: Main orchestrator to initialise the config, bootstrap ALL sync-endpoint-processes and start/stop those in threads 43 | - SyncProcess_: Does orchestrate/a manage ONE sync-endpoint. Selects the strategy on base of the config 44 | - Strategies: See below, specific implementations how to either sync or watch for changes. 45 | 46 | .. _SyncManager: https://github.com/EugenMayer/docker-sync/blob/master/lib/docker_sync/sync_manager.rb 47 | .. _SyncProcess: https://github.com/EugenMayer/docker-sync/blob/master/lib/docker_sync/sync_process.rb 48 | 49 | Sync strategies 50 | --------------- 51 | 52 | 1. To add a new strategy for sync, copy one of those https://github.com/EugenMayer/docker-sync/tree/master/lib/docker_sync/sync_strategy here as your 53 | 2. Implement the general commands as they are implemented for rsync/unison - yes we do not have an strategy interface and no abstract class, since its ruby .. and well :) 54 | 3. Add your strategy here: https://github.com/EugenMayer/docker-sync/blob/master/lib/docker_sync/sync_process.rb#L31 55 | 56 | Thats it. 57 | 58 | Watch strategies 59 | ---------------- 60 | 61 | 1. To add a new strategy for watch, copy one of those https://github.com/EugenMayer/docker-sync/tree/master/lib/docker_sync/watch_strategy here as your 62 | 2. Implement the general commands as they are implemented for fswatch 63 | 3. Add your strategy here: https://github.com/EugenMayer/docker-sync/blob/master/lib/docker_sync/sync_process.rb#L46 64 | 65 | Thats it. 66 | 67 | ---- 68 | 69 | Testing 70 | ======= 71 | 72 | Automated integration tests 73 | --------------------------- 74 | 75 | .. code-block:: shell 76 | 77 | bundle install 78 | bundle exec rspec --format=documentation 79 | 80 | Manual Tests (sync and performance) 81 | ----------------------------------- 82 | 83 | .. tip:: 84 | 85 | You can also use the docker-sync-boilerplate_. 86 | 87 | Pull this repo and then 88 | 89 | .. code-block:: shell 90 | 91 | cd docker-sync/example 92 | thor stack:start 93 | 94 | Open a new shell and run 95 | 96 | .. code-block:: shell 97 | 98 | cd docker-sync/example 99 | echo "NEWVALUE" >> data1/somefile.txt 100 | echo "NOTTHEOTHER" >> data2/somefile.txt 101 | 102 | Check the docker-compose logs and you see that the files are updated. 103 | 104 | Performance write test: 105 | 106 | .. code-block:: shell 107 | 108 | docker exec -i -t fullexample_app time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 109 | 110 | .. _docker-sync-boilerplate: https://github.com/EugenMayer/docker-sync-boilerplate 111 | -------------------------------------------------------------------------------- /docs/miscellaneous/performance.rst: -------------------------------------------------------------------------------- 1 | *********** 2 | Performance 3 | *********** 4 | 5 | Performance 6 | =========== 7 | 8 | OSX 9 | --- 10 | 11 | +--------------------------------+-------------------------+-----------------------+ 12 | | Setup | Native | Docker-Sync | 13 | | | (out of the box) +--------+--------------+ 14 | | | | Unison | native_osx | 15 | +================================+=========================+========+==============+ 16 | | Docker Toolbox - VMware Fusion | 12.31s | 0.24s | n/a (issue_) | 17 | +--------------------------------+-------------------------+--------+--------------+ 18 | | Docker Toolbox - VirtualBox | 3.37s | 0.26s | n/a (issue_) | 19 | +--------------------------------+-------------------------+--------+--------------+ 20 | | Docker for Mac | 20.55s | 0.36s | 0.28s | 21 | +--------------------------------+-------------------------+--------+--------------+ 22 | | Docker for Mac Edge | 18.12s | 0.27s | 0.19s | 23 | +--------------------------------+-------------------------+--------+--------------+ 24 | | Docker for Mac Edge + APFS | 18.15s | 0.38s | 0.37s | 25 | +--------------------------------+-------------------------+--------+--------------+ 26 | | Docker for Mac Edge :cached_ | 17.65s | 0.21s | 0.22s | 27 | +--------------------------------+-------------------------+--------+--------------+ 28 | 29 | Setup and details below at :ref:`performance-tests-2017`. 30 | 31 | .. _issue: https://github.com/EugenMayer/docker-sync/issues/346 32 | .. _cached: https://blog.docker.com/2017/05/user-guided-caching-in-docker-for-mac/ 33 | 34 | Windows 35 | ------- 36 | 37 | Coming soon. 38 | 39 | Linux 40 | ----- 41 | 42 | Coming soon. 43 | 44 | ---- 45 | 46 | .. _performance-tests-2017: 47 | 48 | Performance Tests 2017 49 | ====================== 50 | 51 | Results 52 | ------- 53 | 54 | Test: writing 100MB 55 | 56 | 57 | +--------------------------------+-------------------------+-----------------------+ 58 | | Setup | Native | Docker-Sync | 59 | | | (out of the box) +--------+--------------+ 60 | | | | Unison | native_osx | 61 | +================================+=========================+========+==============+ 62 | | Docker Toolbox - VMware Fusion | 8.70s | 0.22s | n/a (issue_) | 63 | +--------------------------------+-------------------------+--------+--------------+ 64 | | Docker Toolbox - VirtualBox | 3.37s | 0.26s | n/a (issue_) | 65 | +--------------------------------+-------------------------+--------+--------------+ 66 | | Docker for Mac | 18.85s | 0.24s | 0.28s | 67 | +--------------------------------+-------------------------+--------+--------------+ 68 | | Docker for Mac Edge | 18.12s | 0.27s | 0.19s | 69 | +--------------------------------+-------------------------+--------+--------------+ 70 | | Docker for Mac Edge :cached_ | 17.65s | 0.21s | 0.22s | 71 | +--------------------------------+-------------------------+--------+--------------+ 72 | 73 | .. _issue: https://github.com/EugenMayer/docker-sync/issues/346 74 | .. _cached: https://blog.docker.com/2017/05/user-guided-caching-in-docker-for-mac/ 75 | 76 | Those below is how the tests were made and how to reproduce them: 77 | 78 | Setup 79 | ----- 80 | 81 | Test-hardware 82 | ^^^^^^^^^^^^^ 83 | 84 | - i76600u 85 | - 16GB 86 | - SSD 87 | - Sierra 88 | 89 | Docker Toolbox VMware Fusion machine: 90 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 91 | 92 | .. code-block:: shell 93 | 94 | docker-machine create --driver vmwarefusion --vmwarefusion-cpu-count 2 --vmwarefusion-disk-size 50000 --vmwarefusion-memory-size 8000 default 95 | 96 | Docker for Mac 97 | ^^^^^^^^^^^^^^ 98 | 99 | - 8GB 100 | - CPUs 101 | 102 | Native implementations 103 | ---------------------- 104 | 105 | Those tests run without docker-sync or anything, just plain what you get out of the box. 106 | 107 | VirtualBox - Native 108 | ^^^^^^^^^^^^^^^^^^^ 109 | 110 | .. code-block:: shell 111 | 112 | docker-machine create --driver virtualbox --virtualbox-cpu-count 2 --virtualbox-disk-size 20000 --virtualbox-memory "8000" vbox 113 | 114 | .. code-block:: shell 115 | 116 | docker run -it -v /Users/em/test:/var/www alpine time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 117 | 100000+0 records in 118 | 100000+0 records out 119 | real 0m 3.37s 120 | user 0m 0.00s 121 | sys 0m 2.09s 122 | 123 | **3.37s** 124 | 125 | VMware Fusion - Native 126 | ^^^^^^^^^^^^^^^^^^^^^^ 127 | 128 | .. code-block:: shell 129 | 130 | docker run -it -v /Users/em/test:/var/www alpine time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 131 | 100000+0 records in 132 | 100000+0 records out 133 | real 0m 12.32s 134 | user 0m 0.14s 135 | sys 0m 2.22s 136 | 137 | **12.31s** 138 | 139 | Docker for Mac - Native 140 | ^^^^^^^^^^^^^^^^^^^^^^^^ 141 | 142 | - 8GB Ram 143 | - 2 CPUs 144 | 145 | .. code-block:: shell 146 | 147 | docker run -it -v /Users/em/test:/var/www alpine time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 148 | 100000+0 records in 149 | 100000+0 records out 150 | real 0m 18.85s 151 | user 0m 0.11s 152 | sys 0m 1.06s 153 | 154 | **20.55s** 155 | 156 | Docker-sync - Strategy: Native_osx 157 | ---------------------------------- 158 | 159 | Get this repo and this boilerplate project 160 | 161 | .. code-block:: shell 162 | 163 | git clone https://github.com/EugenMayer/docker-sync-boilerplate 164 | cd docker-sync-boilerplate/default 165 | docker-sync-stack start 166 | 167 | Vmware Fusion 168 | ------------- 169 | 170 | .. code-block:: shell 171 | 172 | docker exec -it nativeosx_app-unison_1 time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 173 | 100000+0 records in 174 | 100000+0 records out 175 | real 0m 0.32s 176 | user 0m 0.02s 177 | sys 0m 0.24s 178 | 179 | **0.32s** 180 | 181 | Docker for Mac 182 | -------------- 183 | 184 | .. code-block:: shell 185 | 186 | docker exec -it nativeosx_app-unison_1 time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 187 | 100000+0 records in 188 | 100000+0 records out 189 | real 0m 0.28s 190 | user 0m 0.02s 191 | sys 0m 0.25s 192 | 193 | **0.26s** 194 | 195 | Docker-Sync - Strategy: Unison 196 | ------------------------------ 197 | 198 | Get this repo and this boilerplate project 199 | 200 | .. code-block:: shell 201 | 202 | git clone https://github.com/EugenMayer/docker-sync-boilerplate 203 | cd docker-sync-boilerplate/unison 204 | docker-sync-stack start 205 | 206 | VirtualBox 207 | ---------- 208 | 209 | .. code-block:: shell 210 | 211 | docker exec -it unison_app-unison_1 time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 212 | 100000+0 records in 213 | 100000+0 records out 214 | real 0m 0.26s 215 | user 0m 0.00s 216 | sys 0m 0.23s 217 | 218 | 219 | VMware Fusion 220 | ------------- 221 | 222 | .. code-block:: shell 223 | 224 | docker exec -it unison_app-unison_1 time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 225 | 100000+0 records in 226 | 100000+0 records out 227 | real 0m 0.24s 228 | user 0m 0.01s 229 | sys 0m 0.23s 230 | 231 | Docker for Mac 232 | -------------- 233 | 234 | .. code-block:: shell 235 | 236 | docker exec -it unison_app-unison_1 time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000 237 | 100000+0 records in 238 | 100000+0 records out 239 | real 0m 0.24s 240 | user 0m 0.04s 241 | sys 0m 0.16s 242 | 243 | **0.36s** 244 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme==2.0.0 2 | -------------------------------------------------------------------------------- /docs/troubleshooting/common-issues.rst: -------------------------------------------------------------------------------- 1 | ******************* 2 | Other common issues 3 | ******************* 4 | 5 | Incorrect mount location 6 | ======================== 7 | 8 | Docker-sync uses a named container/volume for synchronizing. There is a chance you may have a conflicting sync name. To verify this, run: 9 | 10 | .. code-block:: shell 11 | 12 | docker container inspect --format '{{(index .Mounts 1).Source}}' "$DEBUG_DOCKER_SYNC" 13 | 14 | If you do not see the path to your directory, this means your mount location is conflicting. To fix this issue: 15 | 16 | 1. Bring your containers down 17 | 2. Perform ``docker-sync clean`` 18 | 3. Bring your containers back up again. 19 | -------------------------------------------------------------------------------- /docs/troubleshooting/native-osx-troubleshooting.rst: -------------------------------------------------------------------------------- 1 | **************************************************** 2 | Advanced troubleshooting for ``native_osx`` strategy 3 | **************************************************** 4 | 5 | .. note:: 6 | 7 | This document is a work in progress. Each time you encounter the scenario below, please revisit this document and `report any new findings`_. 8 | 9 | .. _report any new findings: https://github.com/EugenMayer/docker-sync/issues/410 10 | 11 | The osx_native sync strategy is the fastest sync strategy for docker-sync under Docker4Mac. Unfortunately a recurring issue has emerged where the `sync strategy stops functioning`_. This page is to guide you on how to debug this situation to provide information so that it can be solved. 12 | 13 | .. _sync strategy stops functioning: https://github.com/EugenMayer/docker-sync/issues/410 14 | 15 | Step 1 - Prepare: Identify the docker-sync container involved 16 | ============================================================= 17 | 18 | First, open your docker-sync.yml file and find the sync that has to do with the code that appears to be failing to sync. 19 | 20 | For example, if you have two docker-sync mounts like so: 21 | 22 | .. code-block:: yaml 23 | 24 | syncs: 25 | default-sync: 26 | src: './default-data/' 27 | fullexample-sync: 28 | src: './data1' 29 | 30 | And your file that is not updating is under ``default-data``, then your sync name is ``default-sync``. 31 | 32 | Run this in your terminal (substitute in your sync name) for use in the remaining steps: ``DEBUG_DOCKER_SYNC='default-sync'`` 33 | 34 | Step 2 - Prepare: A file path to check 35 | ====================================== 36 | 37 | Next we're going to assign a file path to a variable for use in the following steps. 38 | 39 | 1. Change into your sync directory (in the example ``cd default-data/``) 40 | 2. Prepare the relative path to your file that does not appear to be updating upon save, example ``some-dir/another-dir/my-file.ext`` 41 | 3. Run the following command with your path substituted in: ``DEBUG_DOCKER_FILE='some-dir/another-dir/my-file.ext'`` 42 | 43 | Step 3 - Reproduction: Verify your host mount works (host_sync) 44 | =============================================================== 45 | 46 | Run this to verify that your file changes have been synced by OSXFS to the sync-container 47 | 48 | .. code-block:: shell 49 | 50 | diff -q "$DEBUG_DOCKER_FILE" <(docker exec "$DEBUG_DOCKER_SYNC" cat "/host_sync/$DEBUG_DOCKER_FILE") 51 | 52 | Usually this should never get broken at all, if it does, you see one of the following messages, the so called host_sync is broken: 53 | 54 | .. code-block:: shell 55 | 56 | Files some-dir/another-dir/my-file.ext and /dev/fd/63 differ 57 | diff: some-dir/another-dir/my-file.ext: No such file or directory 58 | 59 | Step 4 - Reproduction: Verify your changes have been sync by unison (app_sync) 60 | ============================================================================== 61 | 62 | Run this to verify that the changes have been sync from host_sync to app_sync on the container (using unison) 63 | 64 | .. code-block:: shell 65 | 66 | diff -q "$DEBUG_DOCKER_FILE" <(docker exec "$DEBUG_DOCKER_SYNC" cat "/app_sync/$DEBUG_DOCKER_FILE") 67 | 68 | If you see a message one of the messages, this so called app_sync is broken: 69 | 70 | .. code-block:: shell 71 | 72 | Files some-dir/another-dir/my-file.ext and /dev/fd/63 differ 73 | diff: some-dir/another-dir/my-file.ext: No such file or directory 74 | 75 | *If you do not see a message like one of these, then the issue you are encountering is not related to a sync failure and is probably something like caching or some other issue in your application stack, not docker-sync.* 76 | 77 | Step 5 - Reproduction: Unison log 78 | ================================= 79 | 80 | If one of the upper errors occurred, please include the unison logs: 81 | 82 | .. code-block:: shell 83 | 84 | docker exec "$DEBUG_DOCKER_SYNC" tail -n70 /tmp/unison.log 85 | 86 | And paste those on Hastebin_ and include the link in your report 87 | 88 | Step 6 - Reproduction: Ensure you have no conflicts 89 | =================================================== 90 | 91 | Put that into your problematic sync container docker-sync.yml config: 92 | 93 | .. code-block:: shell 94 | 95 | sync_args: "-copyonconflict -debug verbose" 96 | 97 | Restart the stack 98 | 99 | .. code-block:: shell 100 | 101 | docker-sync-stack clean 102 | docker-sync-stack start 103 | 104 | Now do the file test above and see, if next to the file, in host_sync or app_sync a conflict file is created, its called something like conflict 105 | 106 | Also then include the log 107 | 108 | .. code-block:: shell 109 | 110 | docker exec "$DEBUG_DOCKER_SYNC" tail -n70 /tmp/unison.log 111 | 112 | And paste those on Hastebin_ and include the link in your report 113 | 114 | .. _Hastebin: https://hastebin.com 115 | 116 | Step 7 - Report the issue 117 | ========================= 118 | 119 | If the issue still persists, post the results from the above steps in a new Github issue (see an example at `issue #410`)_. 120 | 121 | .. _issue #410: https://github.com/EugenMayer/docker-sync/issues/410 122 | -------------------------------------------------------------------------------- /docs/troubleshooting/sync-stopping.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | Sync stopping 3 | ************* 4 | 5 | Npm / webpack / gulp based projects 6 | =================================== 7 | 8 | Npm, webpack, gulp, and any other tooling that watches, deletes and/or creates a lot of files may cause sync to stop. 9 | 10 | In most cases, this has nothing to do with docker-sync at all, but with OSXFS getting stuck in the FS event queue, which then also stops events for unison in our docker image (linux, so inode events) and thus breaks syncing. 11 | 12 | - Run ``npm i``, ``composer install``, and the likes **before** ``docker-sync start``. This way we avoid tracking unnecessary FS events prior to start. 13 | 14 | - Sync only necessary folders, e.g. ``src/`` folders. Restructure your project layout if needed. 15 | 16 | 17 | Other reported solutions 18 | ======================== 19 | 20 | 1. Run ``docker-sync stop && docker-sync start`` 21 | 2. Run ``docker-sync stop && docker-sync clean && docker-sync start`` 22 | 3. Manually going to the unison docker container and executing ``kill -1 [PID]`` on the unison process (`suggested by @grigoryosifov`_) 23 | 4. Sometimes, the OSXFS itself gets stuck. Docker for Mac restart maybe the only option. Sometimes, an OS restart is the only option. 24 | 25 | .. _suggested by @grigoryosifov: https://github.com/EugenMayer/docker-sync/issues/646#issuecomment-466991460 26 | -------------------------------------------------------------------------------- /example/create-data/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | ADD ./entrypoint.sh /entrypoint.sh 4 | RUN chmod +x /entrypoint.sh 5 | 6 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /example/create-data/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "$1" 4 | 5 | if [ "$1" == "watch" ]; then 6 | run_with_user=www-data 7 | 8 | # prevent error if uid is already in use 9 | if ! cut -d: -f3 /etc/passwd | grep -q $(stat -c '%u' /data); then 10 | adduser -u $(stat -c '%u' /data) -D www-data 11 | else 12 | $run_with_user=$(awk -F: "/:$UNISON_OWNER_UID:/{print \$1}" /etc/passwd) 13 | fi 14 | 15 | while true; do 16 | su $run_with_userr -c 'date >> /data/filefromcontainer.txt'; 17 | sleep 5; 18 | done 19 | fi 20 | 21 | 22 | 23 | exec "$@" -------------------------------------------------------------------------------- /example/data1/somefile.txt: -------------------------------------------------------------------------------- 1 | aaaa 2 | -------------------------------------------------------------------------------- /example/data2/somefile.txt: -------------------------------------------------------------------------------- 1 | faa 2 | faa 3 | faa 4 | faa 5 | faa 6 | -------------------------------------------------------------------------------- /example/data3/filefromhost.txt: -------------------------------------------------------------------------------- 1 | File from host -------------------------------------------------------------------------------- /example/data4/filefromhost.txt: -------------------------------------------------------------------------------- 1 | File from host -------------------------------------------------------------------------------- /example/default/app/index.html: -------------------------------------------------------------------------------- 1 | inital, now change app/index.html and see if this will change me 2 | assa 3 | -------------------------------------------------------------------------------- /example/default/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | # this is our development docker-compose building on top of the production docker-compose, just mounting 2 | # the sync image - not redefining anything else 3 | 4 | version: "2" 5 | services: 6 | app-native-osx: 7 | volumes: 8 | - appcode-native-osx-sync:/var/www:nocopy # nocopy is important 9 | 10 | # that the important thing 11 | volumes: 12 | appcode-native-osx-sync: 13 | external: true 14 | -------------------------------------------------------------------------------- /example/default/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This is our production compose file - not changed for docker-sync or similar 2 | 3 | version: "2" 4 | services: 5 | app-native-osx: 6 | image: alpine 7 | command: 'watch -n3 cat /var/www/index.html' 8 | -------------------------------------------------------------------------------- /example/default/docker-sync.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | options: 4 | verbose: true 5 | syncs: 6 | appcode-native-osx-sync: # tip: add -sync and you keep consistent names as a convention 7 | src: './app' 8 | # sync_strategy: 'native_osx' # not needed, this is the default now 9 | sync_excludes: ['ignored_folder', '.ignored_dot_folder'] 10 | -------------------------------------------------------------------------------- /example/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | # Development environment reusing production and just adding mounts for docker-sync 2 | version: "2" 3 | services: 4 | rsyncapp: 5 | environment: 6 | RAILS_ENV: 'development' 7 | volumes: 8 | - rsync-sync:/var/www:nocopy # will be mounted on /var/www 9 | unison: 10 | volumes: 11 | - unison-sync:/app/code:nocopy # will be mounted on /app/code 12 | volumes: 13 | rsync-sync: 14 | external: true 15 | unison-sync: 16 | external: true 17 | -------------------------------------------------------------------------------- /example/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # production docker-compose file without any reference to docker-sync 2 | 3 | version: "2" 4 | services: 5 | rsyncapp: 6 | image: alpine 7 | container_name: 'rsyncapp' 8 | command: ['watch', '-n1', 'cat /var/www/somefile.txt'] 9 | unisononsidedapp: 10 | image: alpine 11 | container_name: 'unisononsidedapp' 12 | command: ['watch', '-n1', 'cat /app/code/somefile.txt'] 13 | unison: 14 | image: alpine 15 | container_name: 'unison' 16 | command: ['watch', '-n1', 'cat /app/code/somefile.txt'] 17 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | !!!! Important hint !!!!! 2 | 3 | > Please do not create issues for [docker](https://docs.docker.com/) or [docker-compose](https://docs.docker.com/compose/reference) related questions. Ensure you understand the tools properly and consult their documentation first. Please understand what [docker volumes](https://docs.docker.com/engine/admin/volumes/volumes/) / [docker-compose volumes](https://docs.docker.com/compose/compose-file/compose-file-v2/#specifying-byte-values) 4 | 5 | > Please be sure to have a look at our [documentation](https://github.com/EugenMayer/docker-sync/wiki) too, especially [configuration](https://github.com/EugenMayer/docker-sync/wiki/2.-Configuration) and our starting point the [boilerplate](https://github.com/EugenMayer/docker-sync-boilerplate) 6 | 7 | ### Error/Feature Requestion/Docs 8 | ? 9 | 10 | ### Docker Driver 11 | ? (d4m or docker-machine+vbox/fusion) 12 | 13 | ### Sync strategy 14 | ? (default/native_osx/unison/rsync) 15 | 16 | ### your docker-sync.yml 17 | 18 | ### OS 19 | ? (OSX Version/Linux) 20 | 21 | -------------------------------------------------------------------------------- /lib/docker-sync.rb: -------------------------------------------------------------------------------- 1 | require 'docker-sync/environment' 2 | require 'docker-sync/dependencies' 3 | require 'docker-sync/config/config_locator' 4 | require 'docker-sync/config/global_config' 5 | require 'docker-sync/config/project_config' 6 | -------------------------------------------------------------------------------- /lib/docker-sync/command.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'pty' 3 | rescue LoadError 4 | # for Windows support, tolerate a missing PTY module 5 | end 6 | 7 | module DockerSync 8 | # based on `Backticks::Command` from `Backticks` gem 9 | class Command 10 | FOREVER = 86_400 * 365 11 | CHUNK = 1_024 12 | 13 | # @return [Integer] child process ID 14 | attr_reader :pid 15 | 16 | # @return [nil,Process::Status] result of command if it has ended; nil if still running 17 | attr_reader :status 18 | 19 | # @return [String] all input that has been captured so far 20 | attr_reader :captured_input 21 | 22 | # @return [String] all output that has been captured so far 23 | attr_reader :captured_output 24 | 25 | # @return [String] all output to stderr that has been captured so far 26 | attr_reader :captured_error 27 | 28 | # Run a command. The parameters are same as `Kernel#spawn`. 29 | # 30 | # Usage: 31 | # run('docker-compose', '--file=joe.yml', 'up', '-d', 'mysvc') 32 | def self.run(*argv, dir: nil) 33 | nopty = !defined?(PTY) 34 | 35 | stdin_r, stdin = nopty ? IO.pipe : PTY.open 36 | stdout, stdout_w = nopty ? IO.pipe : PTY.open 37 | stderr, stderr_w = IO.pipe 38 | 39 | chdir = dir || Dir.pwd 40 | pid = spawn(*argv, in: stdin_r, out: stdout_w, err: stderr_w, chdir: chdir) 41 | 42 | stdin_r.close 43 | stdout_w.close 44 | stderr_w.close 45 | 46 | self.new(pid, stdin, stdout, stderr) 47 | end 48 | 49 | def initialize(pid, stdin, stdout, stderr) 50 | @pid = pid 51 | @stdin = stdin 52 | @stdout = stdout 53 | @stderr = stderr 54 | @status = nil 55 | 56 | @captured_input = String.new(encoding: Encoding::BINARY) 57 | @captured_output = String.new(encoding: Encoding::BINARY) 58 | @captured_error = String.new(encoding: Encoding::BINARY) 59 | end 60 | 61 | def success? 62 | status.success? 63 | end 64 | 65 | def join(limit = FOREVER) 66 | return self if @status 67 | 68 | tf = Time.now + limit 69 | until (t = Time.now) >= tf 70 | capture(tf - t) 71 | res = Process.waitpid(@pid, Process::WNOHANG) 72 | if res 73 | @status = $? 74 | return self 75 | end 76 | end 77 | 78 | nil 79 | end 80 | 81 | private 82 | 83 | def capture(limit) 84 | streams = [@stdout, @stderr] 85 | streams << STDIN if STDIN.tty? 86 | 87 | ready, = IO.select(streams, [], [], limit) 88 | 89 | # proxy STDIN to child's stdin 90 | if ready && ready.include?(STDIN) 91 | data = STDIN.readpartial(CHUNK) rescue nil 92 | if data 93 | @captured_input << data 94 | @stdin.write(data) 95 | else 96 | # our own STDIN got closed; proxy this fact to the child 97 | @stdin.close unless @stdin.closed? 98 | end 99 | end 100 | 101 | # capture child's stdout and maybe proxy to STDOUT 102 | if ready && ready.include?(@stdout) 103 | data = @stdout.readpartial(CHUNK) rescue nil 104 | if data 105 | @captured_output << data 106 | STDOUT.write(data) 107 | fresh_output = data 108 | end 109 | end 110 | 111 | # capture child's stderr and maybe proxy to STDERR 112 | if ready && ready.include?(@stderr) 113 | data = @stderr.readpartial(CHUNK) rescue nil 114 | if data 115 | @captured_error << data 116 | STDERR.write(data) 117 | end 118 | end 119 | fresh_output 120 | rescue Interrupt 121 | # Proxy Ctrl+C to the child 122 | Process.kill('INT', @pid) rescue nil 123 | raise 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/docker-sync/compose.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | require 'docker-sync/docker_compose_session' 3 | 4 | class ComposeManager 5 | include Thor::Shell 6 | @compose_session 7 | @global_options 8 | def initialize(global_options) 9 | @global_options = global_options 10 | 11 | ### production docker-compose.yml 12 | compose_files = [File.expand_path('docker-compose.yml')] 13 | if @global_options.key?('compose-file-path') 14 | compose_files = [] # replace 15 | apply_path_settings(compose_files, @global_options['compose-file-path'], 'compose-file-path') 16 | end 17 | 18 | ### development docker-compose-dev.yml 19 | if @global_options.key?('compose-dev-file-path') 20 | # explicit path given 21 | apply_path_settings(compose_files, @global_options['compose-dev-file-path'], 'compose-dev-file-path') 22 | say_status 'ok',"Found explicit docker-compose-dev.yml and using it from #{@global_options['compose-dev-file-path']}", :green 23 | else 24 | # try to find docker-compose-dev.yml 25 | e = compose_files.to_enum 26 | production_compose_file = File.expand_path(e.peek) 27 | working_dir = File.dirname(production_compose_file) 28 | compose_dev_path = "#{working_dir}/docker-compose-dev.yml" 29 | if File.exist?(compose_dev_path) 30 | say_status 'ok',"Found implicit docker-compose-dev.yml and using it from #{compose_dev_path}", :green 31 | compose_files.push compose_dev_path 32 | end 33 | end 34 | @compose_session = DockerSync::DockerComposeSession.new(dir: './', files: compose_files) 35 | end 36 | 37 | def run 38 | say_status 'ok','starting compose',:green 39 | options = Hash.new 40 | if @global_options['compose-force-build'] 41 | options[:build] = true 42 | end 43 | @compose_session.up(**options) 44 | end 45 | 46 | def stop 47 | @compose_session.stop 48 | end 49 | 50 | def clean 51 | @compose_session.down 52 | end 53 | 54 | protected 55 | def apply_path_settings(compose_files, settings, error_key) 56 | if settings.kind_of?(Array) 57 | apply_filepaths(compose_files, settings, error_key) 58 | else 59 | apply_filepath(compose_files, settings, error_key) 60 | end 61 | end 62 | 63 | private 64 | def apply_filepath(array, filepath, error_key) 65 | path = File.expand_path(filepath) 66 | unless File.exist?(path) 67 | raise("Your referenced docker-compose file in docker-sync.yml was not found at #{error_key}") 68 | end 69 | array.push path 70 | end 71 | 72 | def apply_filepaths(array, filepath_array, error_key) 73 | filepath_array.each do |filepath| 74 | apply_filepath(array, filepath, error_key) 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/docker-sync/config/config_locator.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module DockerSync 4 | # helps us loading our config files, GlobalConfig and ProjectConfig 5 | module ConfigLocator 6 | ERROR_MISSING_PROJECT_CONFIG = 7 | 'No docker-sync.yml configuration found in your path ( traversing up ) '\ 8 | 'Did you define it for your project?'.freeze 9 | 10 | class << self 11 | attr_accessor :global_config_path 12 | # @return [String] The path to the global config location 13 | def current_global_config_path 14 | path = global_config_path 15 | path = File.expand_path('~/.docker-sync-global.yml') if path.nil? 16 | path 17 | end 18 | 19 | # @return [String] the path to the project configuration found 20 | def lookup_project_config_path 21 | files = project_config_find 22 | 23 | raise ERROR_MISSING_PROJECT_CONFIG if files.empty? 24 | 25 | files.pop 26 | end 27 | 28 | private 29 | 30 | 31 | # this has been ruthlessly stolen from Thor/runner.rb - please do not hunt me for that :) 32 | # returns a list of file paths matching the docker-sync.yml file. The return the first one we find while traversing 33 | # the folder tree up 34 | # @return [Array] 35 | def project_config_find(skip_lookup = false) 36 | # Finds docker-sync.yml by traversing from your current directory down to the root 37 | # directory of your system. If at any time we find a docker-sync.yml file, we stop. 38 | # 39 | # 40 | # ==== Example 41 | # 42 | # If we start at /Users/wycats/dev/thor ... 43 | # 44 | # 1. /Users/wycats/dev/thor 45 | # 2. /Users/wycats/dev 46 | # 3. /Users/wycats <-- we find a docker-sync.yml here, so we stop 47 | # 48 | # Suppose we start at c:\Documents and Settings\james\dev\docker-sync ... 49 | # 50 | # 1. c:\Documents and Settings\james\dev\docker-sync.yml 51 | # 2. c:\Documents and Settings\james\dev 52 | # 3. c:\Documents and Settings\james 53 | # 4. c:\Documents and Settings 54 | # 5. c:\ <-- no docker-sync.yml found! 55 | # 56 | docker_sync_files = [] 57 | 58 | unless skip_lookup 59 | Pathname.pwd.ascend do |path| 60 | docker_sync_files = globs_for_project_config(path).map { |g| Dir[g] }.flatten 61 | break unless docker_sync_files.empty? 62 | end 63 | end 64 | 65 | docker_sync_files 66 | end 67 | 68 | # Where to look for docker-sync.yml files. 69 | # 70 | def globs_for_project_config(path) 71 | path = escape_globs(path) 72 | ["#{path}/docker-sync.yml"] 73 | end 74 | 75 | def escape_globs(path) 76 | path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&') 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/docker-sync/config/config_serializer.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'dotenv' 3 | 4 | module DockerSync 5 | module ConfigSerializer 6 | class << self 7 | # @param [String] config_path path to the yaml configuration to load 8 | # @return [Object] returns a Yaml hashmap, expaneded by ENV vars 9 | def default_deserializer_file(config_path) 10 | config_string = File.read(config_path) 11 | default_deserializer_string(config_string) 12 | end 13 | 14 | # @param [String] config_string the configuration string inf yaml format 15 | # @return [Object] a yaml hashmap 16 | def default_deserializer_string(config_string) 17 | deserialize_config( expand_env_variables(config_string) ) 18 | end 19 | 20 | private 21 | 22 | # Replaces our tokens, in this case all ENV variables we defined. Find those in the string an replace 23 | # them with then values from our ENV, including the dotenv file 24 | # @param [String] config_string 25 | # @return [String] 26 | def expand_env_variables(config_string) 27 | load_dotenv 28 | 29 | env_hash = {} 30 | ENV.each {|k,v| env_hash[k.to_sym] = v } 31 | config_string.gsub!('${', '%{') 32 | config_string % env_hash 33 | end 34 | 35 | 36 | # deserializes the configuration string, right now as a yaml formatted string 37 | # @param [String] config_string 38 | # @return [Object] 39 | def deserialize_config(config_string) 40 | # noinspection RubyResolve 41 | YAML.load(config_string) 42 | end 43 | 44 | # Loads the dotenv file but also lets us overide the source not being .env but anything you put 45 | # into the ENV variable DOCKER_SYNC_ENV_FILE 46 | # @return [Object] 47 | def load_dotenv 48 | # TODO: ensure we do this once only 49 | env_file = ENV.fetch('DOCKER_SYNC_ENV_FILE', '.env') 50 | 51 | # noinspection RubyResolve 52 | Dotenv.load(env_file) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/docker-sync/config/global_config.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require 'forwardable' 3 | require 'date' 4 | 5 | require 'docker-sync/config/config_locator' 6 | require 'docker-sync/config/config_serializer' 7 | 8 | module DockerSync 9 | class GlobalConfig 10 | extend Forwardable 11 | include Singleton 12 | 13 | # noinspection RubyStringKeysInHashInspection 14 | DEFAULTS = { 15 | 'update_check' => true, 16 | 'update_last_check' => DateTime.new(2001, 1, 1).iso8601(9), 17 | 'update_enforce' => true, 18 | 'upgrade_status' => '', 19 | }.freeze 20 | 21 | attr_reader :config 22 | private :config 23 | 24 | def_delegators :@config, :[], :to_h 25 | 26 | def self.load; instance end 27 | 28 | def initialize 29 | load_global_config 30 | end 31 | 32 | def load_global_config 33 | @config_path = DockerSync::ConfigLocator.current_global_config_path 34 | if File.exist?(@config_path) 35 | @config = DockerSync::ConfigSerializer.default_deserializer_file(@config_path) 36 | end 37 | 38 | unless @config 39 | @config = DEFAULTS.dup 40 | @first_run = true 41 | end 42 | end 43 | 44 | def first_run? 45 | @first_run 46 | end 47 | 48 | # @param [Object] updates 49 | # Updates and saves the configuration back to the file 50 | def update!(updates) 51 | @config.merge!(updates) 52 | 53 | File.open(@config_path, 'w') {|f| f.write @config.to_yaml } 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/docker-sync/config/project_config.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require 'docker-sync/config/config_locator' 3 | require 'docker-sync/config/config_serializer' 4 | require 'forwardable' 5 | require 'docker-sync/environment' 6 | 7 | module DockerSync 8 | class ProjectConfig 9 | extend Forwardable 10 | 11 | REQUIRED_CONFIG_VERSION = '2'.freeze 12 | 13 | ERROR_MISSING_CONFIG_VERSION = 14 | "Your docker-sync.yml file does not include a version: \"#{REQUIRED_CONFIG_VERSION}\""\ 15 | '(Add this if you migrated from docker-sync 0.1.x)'.freeze 16 | 17 | ERROR_MISMATCH_CONFIG_VERSION = 18 | 'Your docker-sync.yml file does not match the required version '\ 19 | "(#{REQUIRED_CONFIG_VERSION}).".freeze 20 | 21 | ERROR_MISSING_SYNCS = 'no syncs defined'.freeze 22 | 23 | attr_reader :config, :config_path 24 | private :config 25 | 26 | def_delegators :@config, :[], :to_h 27 | 28 | def initialize(config_path: nil, config_string: nil) 29 | if config_string.nil? 30 | if config_path.nil? || config_path.empty? 31 | config_path = DockerSync::ConfigLocator.lookup_project_config_path 32 | end 33 | 34 | load_project_config(config_path) 35 | else 36 | @config = DockerSync::ConfigSerializer.default_deserializer_string(config_string) 37 | @config_path = nil 38 | end 39 | 40 | validate_config! 41 | normalize_config! 42 | end 43 | 44 | def load_project_config(config_path = nil) 45 | @config_path = config_path 46 | return unless File.exist?(@config_path) 47 | @config = DockerSync::ConfigSerializer.default_deserializer_file(@config_path) 48 | end 49 | 50 | def unison_required? 51 | # noinspection RubyUnusedLocalVariable 52 | config['syncs'].any? { |name, sync_config| 53 | sync_config['sync_strategy'] == 'unison' || sync_config['watch_strategy'] == 'unison' 54 | } 55 | end 56 | 57 | def rsync_required? 58 | # noinspection RubyUnusedLocalVariable 59 | config['syncs'].any? { |name, sync_config| 60 | sync_config['sync_strategy'] == 'rsync' 61 | } 62 | end 63 | 64 | def fswatch_required? 65 | # noinspection RubyUnusedLocalVariable 66 | config['syncs'].any? { |name, sync_config| 67 | sync_config['watch_strategy'] == 'fswatch' 68 | } 69 | end 70 | 71 | private 72 | 73 | def validate_config! 74 | raise error_missing_given_config if config.nil? 75 | raise ERROR_MISSING_CONFIG_VERSION unless config.key?('version') 76 | raise ERROR_MISMATCH_CONFIG_VERSION unless config['version'].to_s == REQUIRED_CONFIG_VERSION 77 | raise ERROR_MISSING_SYNCS unless config.key?('syncs') 78 | 79 | validate_syncs_config! 80 | end 81 | 82 | def validate_syncs_config! 83 | config['syncs'].each do |name, sync_config| 84 | validate_sync_config(name, sync_config) 85 | end 86 | end 87 | 88 | def validate_sync_config(name, sync_config) 89 | config_mandatory = %w[src] 90 | #TODO: Implement autodisovery for other strategies 91 | config_mandatory.push('sync_host_port') if sync_config['sync_strategy'] == 'rsync' 92 | config_mandatory.each do |key| 93 | raise ("#{name} does not have #{key} configuration value set - this is mandatory") unless sync_config.key?(key) 94 | end 95 | end 96 | 97 | def error_missing_given_config 98 | "Config could not be loaded from #{config_path} - it does not exist" 99 | end 100 | 101 | def normalize_config! 102 | normalize_options_config! 103 | 104 | config['syncs'].each do |name, sync_config| 105 | config['syncs'][name] = normalize_sync_config(sync_config) 106 | end 107 | end 108 | 109 | def normalize_options_config! 110 | config['options'] = { 111 | 'project_root' => 'pwd', 112 | }.merge(config['options'] || {}) 113 | end 114 | 115 | def normalize_sync_config(sync_config) 116 | { 117 | 'sync_strategy' => sync_strategy_for(sync_config), 118 | 'watch_strategy' => watch_strategy_for(sync_config) 119 | }.merge(sync_config).merge( 120 | 'src' => expand_path(sync_config['src']), 121 | ) 122 | end 123 | 124 | def sync_strategy_for(sync_config) 125 | sync_strategy = sync_config['sync_strategy'] 126 | 127 | if %w(rsync unison native native_osx).include?(sync_strategy) 128 | sync_strategy 129 | else 130 | default_sync_strategy 131 | end 132 | end 133 | 134 | def watch_strategy_for(sync_config) 135 | watch_strategy = sync_config['watch_strategy'] 136 | watch_strategy = 'dummy' if watch_strategy == 'disable' 137 | 138 | if %w(fswatch unison dummy).include?(watch_strategy) 139 | watch_strategy 140 | else 141 | default_watch_strategy(sync_config) 142 | end 143 | end 144 | 145 | def default_sync_strategy 146 | return 'native' if Environment.linux? 147 | return 'native_osx' if Environment.mac? && Dependencies::Docker::Driver.docker_for_mac? 148 | return 'unison' if Environment.mac? 149 | end 150 | 151 | def default_watch_strategy(sync_config) 152 | case sync_strategy_for(sync_config) 153 | when 'rsync' then 'fswatch' 154 | when 'unison' then 'unison' 155 | when 'native' then 'dummy' 156 | when 'native_osx' then 'remotelogs' 157 | else raise "you shouldn't be here" 158 | end 159 | end 160 | 161 | def expand_path(path) 162 | Dir.chdir(project_root) { 163 | # [nbr] convert the sync src from relative to absolute path 164 | # preserve '/' as it may be significant to the sync cmd 165 | absolute_path = File.expand_path(path) 166 | absolute_path << '/' if path.end_with?('/') 167 | absolute_path 168 | } 169 | end 170 | 171 | def project_root 172 | if use_config_path_for_project_root? 173 | File.dirname(@config_path) 174 | else 175 | Dir.pwd 176 | end 177 | end 178 | 179 | def use_config_path_for_project_root? 180 | config['options']['project_root'] == 'config_path' && !(@config_path.nil? || @config_path.empty?) 181 | end 182 | end 183 | end 184 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | require 'thor/shell' 3 | 4 | Dir[ 5 | File.join(__dir__, 'dependencies', 'package_managers', 'base.rb'), 6 | File.join(__dir__, 'dependencies', '**', '*.rb') 7 | ].each { |f| require f } 8 | 9 | module DockerSync 10 | module Dependencies 11 | UNSUPPORTED_OPERATING_SYSTEM = 'Unsupported operating system. Are you sure you need DockerSync?'.freeze 12 | 13 | def self.ensure_all!(config) 14 | return if ENV['DOCKER_SYNC_SKIP_DEPENDENCIES_CHECK'] 15 | return ensure_all_for_mac!(config) if Environment.mac? 16 | return ensure_all_for_linux!(config) if Environment.linux? 17 | return ensure_all_for_freebsd!(config) if Environment.freebsd? 18 | raise(UNSUPPORTED_OPERATING_SYSTEM) 19 | end 20 | 21 | def self.ensure_all_for_mac!(config) 22 | PackageManager.ensure! 23 | Docker.ensure! 24 | Unison.ensure! if config.unison_required? 25 | Rsync.ensure! if config.rsync_required? 26 | Fswatch.ensure! if config.fswatch_required? 27 | end 28 | 29 | def self.ensure_all_for_linux!(config) 30 | Docker.ensure! 31 | Fswatch.forbid! if config.fswatch_required? 32 | end 33 | 34 | def self.ensure_all_for_freebsd!(config) 35 | Docker.ensure! 36 | Unison.ensure! if config.unison_required? 37 | Rsync.ensure! if config.rsync_required? 38 | Fswatch.forbid! if config.fswatch_required? 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/docker.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module Docker 4 | DOCKER_NOT_AVAILABLE = 'Could not find Docker. Please install it (see https://docs.docker.com/compose/install/) and try again.'.freeze 5 | DOCKER_NOT_RUNNING = 'No docker daemon seems to be running. Did you start docker-engine / docker-for-mac / docker-machine?'.freeze 6 | 7 | def self.available? 8 | return @available if defined? @available 9 | @available = find_executable0('docker') 10 | end 11 | 12 | def self.running? 13 | return @running if defined? @running 14 | @running = system('docker ps > /dev/null 2>&1') 15 | end 16 | 17 | def self.ensure! 18 | raise(DOCKER_NOT_AVAILABLE) unless available? 19 | raise(DOCKER_NOT_RUNNING) unless running? 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/docker_driver.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module Docker 4 | module Driver 5 | def self.docker_for_mac? 6 | return false unless Environment.mac? 7 | return @docker_for_mac if defined? @docker_for_mac 8 | 9 | # com.docker.hyperkit for old virtualization engine 10 | # com.docker.virtualization for new virtualization engine 11 | # see https://docs.docker.com/desktop/mac/#enable-the-new-apple-virtualization-framework 12 | @docker_for_mac = Environment.system('pgrep -q com.docker.hyperkit') || Environment.system('pgrep -q com.docker.virtualization') 13 | end 14 | 15 | def self.docker_toolbox? 16 | return false unless Environment.mac? || Environment.freebsd? 17 | return false unless find_executable0('docker-machine') 18 | return @docker_toolbox if defined? @docker_toolbox 19 | @docker_toolbox = Environment.system('docker info | grep -q "Operating System: Boot2Docker"') 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/fswatch.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module Fswatch 4 | UNSUPPORTED = 'Fswatch is not expected to run on platforms other then MacOS' 5 | 6 | def self.available? 7 | forbid! unless Environment.mac? 8 | return @available if defined? @available 9 | @available = find_executable0('fswatch') 10 | end 11 | 12 | def self.ensure! 13 | return if available? 14 | 15 | PackageManager.install_package('fswatch') 16 | puts "please restart docker sync so the installation of fswatch takes effect" 17 | exit(1) 18 | end 19 | 20 | def self.forbid! 21 | raise UNSUPPORTED 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/package_manager.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | module DockerSync 4 | module Dependencies 5 | module PackageManager 6 | class << self 7 | extend Forwardable 8 | def_delegators :package_manager, :available?, :ensure!, :install_package 9 | end 10 | 11 | def self.package_manager 12 | return @package_manager if defined? @package_manager 13 | supported_package_managers.each do |package_manager| 14 | return @package_manager = package_manager if package_manager.available? 15 | end 16 | @package_manager = PackageManager::None 17 | end 18 | 19 | def self.supported_package_managers 20 | ObjectSpace.each_object(::Class).select { |klass| klass < self::Base && klass != self::None } 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/package_managers/apt.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module PackageManager 4 | class Apt < Base 5 | APT_NOT_AVAILABLE = 'APT is not installed. Please install it and try again.'.freeze 6 | 7 | def self.available? 8 | return @available if defined? @available 9 | @available = find_executable0('apt-get') 10 | end 11 | 12 | def self.ensure! 13 | raise(APT_NOT_AVAILABLE) unless available? 14 | end 15 | 16 | private 17 | 18 | def install_cmd 19 | "apt-get install -y #{package}" 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/package_managers/base.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | module DockerSync 4 | module Dependencies 5 | module PackageManager 6 | class Base 7 | DID_NOT_INSTALL_PACKAGE = 'Did not install required package. Please install it manually and try again.'.freeze 8 | FAILED_TO_INSTALL_PACKAGE = 'Failed to install required package. Please install it manually and try again.'.freeze 9 | 10 | extend Forwardable 11 | def_delegators :"Thor::Shell::Color.new", :say_status, :yes? 12 | 13 | attr_reader :package 14 | 15 | def self.install_package(*args) 16 | ensure! 17 | new(*args).send(:install_package) 18 | end 19 | 20 | def initialize(package) 21 | @package = package 22 | end 23 | 24 | private 25 | 26 | def install_package 27 | say_status 'warning', "Could not find #{package}. Trying to install it now.", :yellow 28 | ask_user_confirmation 29 | say_status 'command', install_cmd, :white 30 | raise(FAILED_TO_INSTALL_PACKAGE) unless perform_installation 31 | end 32 | 33 | def ask_user_confirmation 34 | raise(DID_NOT_INSTALL_PACKAGE) unless yes?("Install #{package} for you? [y/N]") 35 | end 36 | 37 | def perform_installation 38 | Environment.system(install_cmd) 39 | end 40 | 41 | def install_cmd 42 | raise NotImplementedError 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/package_managers/brew.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module PackageManager 4 | class Brew < Base 5 | BREW_NOT_AVAILABLE = 'Brew is not installed. Please install it (see https://brew.sh) and try again.'.freeze 6 | 7 | def self.available? 8 | return @available if defined? @available 9 | @available = find_executable0('brew') 10 | end 11 | 12 | def self.ensure! 13 | raise(BREW_NOT_AVAILABLE) unless available? 14 | end 15 | 16 | private 17 | 18 | def install_cmd 19 | "brew install #{package}" 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/package_managers/none.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module PackageManager 4 | class None < Base 5 | NO_PACKAGE_MANAGER_AVAILABLE = 'No package manager was found. Please either install one of those supported (brew, apt, rpm) or install all dependencies manually.'.freeze 6 | 7 | def self.available? 8 | @available ||= true 9 | end 10 | 11 | def self.ensure! 12 | # noop 13 | end 14 | 15 | private 16 | 17 | def install_cmd 18 | raise(NO_PACKAGE_MANAGER_AVAILABLE) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/package_managers/pkg.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module PackageManager 4 | class Pkg < Base 5 | PKG_NOT_AVAILABLE = 'PKG is not installed. Please install it and try again.'.freeze 6 | 7 | def self.available? 8 | return @available if defined? @available 9 | @available = find_executable0('pkg') 10 | end 11 | 12 | def self.ensure! 13 | raise(PKG_NOT_AVAILABLE) unless available? 14 | end 15 | 16 | private 17 | 18 | def install_cmd 19 | "pkg install -y #{package}" 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/package_managers/yum.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module PackageManager 4 | class Yum < Base 5 | YUM_NOT_AVAILABLE = 'Yum is not installed. Please install it and try again.'.freeze 6 | 7 | def self.available? 8 | return @available if defined? @available 9 | @available = find_executable0('yum') 10 | end 11 | 12 | def self.ensure! 13 | raise(YUM_NOT_AVAILABLE) unless available? 14 | end 15 | 16 | private 17 | 18 | def install_cmd 19 | "yum install -y #{package}" 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/rsync.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module Rsync 4 | def self.available? 5 | find_executable0('rsync') 6 | end 7 | 8 | def self.ensure! 9 | PackageManager.install_package('rsync') unless available? 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/unison.rb: -------------------------------------------------------------------------------- 1 | module DockerSync 2 | module Dependencies 3 | module Unison 4 | def self.available? 5 | find_executable0('unison') 6 | end 7 | 8 | def self.ensure! 9 | PackageManager.install_package('unison') unless available? 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/docker-sync/dependencies/unox.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | module DockerSync 4 | module Dependencies 5 | module Unox 6 | LEGACY_UNOX_WARNING = 'You installed unison-fsmonitor (unox) the old legacy way (i.e. not using brew). We need to fix that.'.freeze 7 | FAILED_TO_REMOVE_LEGACY_UNOX = 'Failed to remove legacy unison-fsmonitor (unox). Please delete /usr/local/bin/unison-fsmonitor manually and try again.'.freeze 8 | UNSUPPORTED_FSMONITOR = 'You are using unsupported version of unison-fsmonitor, consider installing eugenmayer/dockersync/unox instead'.freeze 9 | 10 | class << self 11 | extend Forwardable 12 | def_delegators :"Thor::Shell::Color.new", :say_status, :yes? 13 | end 14 | 15 | def self.available? 16 | # should never have been called anyway - fix the call that it should check for the OS 17 | raise 'Unox cannot be available for platforms other than MacOS' unless Environment.mac? 18 | 19 | return true if brew_package_installed?('unox') 20 | return false unless brew_package_installed?('unison-fsmonitor') 21 | 22 | say_status 'warning', UNSUPPORTED_FSMONITOR, :yellow unless @unsupported_fsmonitor_warning_displayed 23 | @unsupported_fsmonitor_warning_displayed = true 24 | true 25 | end 26 | 27 | def self.ensure! 28 | return if available? 29 | raise 'Unox cannot be installed on platforms other than MacOS' unless Environment.mac? 30 | 31 | cleanup_non_brew_version! 32 | PackageManager.install_package('eugenmayer/dockersync/unox') 33 | end 34 | 35 | def self.cleanup_non_brew_version! 36 | return unless non_brew_version_installed? 37 | uninstall_cmd = 'sudo rm -f /usr/local/bin/unison-fsmonitor' 38 | say_status 'warning', LEGACY_UNOX_WARNING, :yellow 39 | raise(FAILED_TO_REMOVE_LEGACY_UNOX) unless yes?('Uninstall legacy unison-fsmonitor (unox)? (y/N)') 40 | say_status 'command', uninstall_cmd, :white 41 | Environment.system(uninstall_cmd) 42 | end 43 | 44 | def self.non_brew_version_installed? 45 | !available? && File.exist?('/usr/local/bin/unison-fsmonitor') 46 | end 47 | 48 | def self.brew_package_installed?(name) 49 | cmd = "brew list #{name} > /dev/null 2>&1" 50 | Environment.system(cmd) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/docker-sync/docker_compose_session.rb: -------------------------------------------------------------------------------- 1 | require 'docker-sync/command' 2 | 3 | module DockerSync 4 | # based on `Docker::Compose::Compose` from `docker-compose` gem 5 | class DockerComposeSession 6 | def initialize(dir: nil, files: nil) 7 | @dir = dir 8 | @files = files || [] # Array[String] 9 | @last_command = nil 10 | end 11 | 12 | def up(build: false) 13 | args = [] 14 | args << '--build' if build 15 | 16 | run!('up', *args) 17 | end 18 | 19 | def stop 20 | run!('stop') 21 | end 22 | 23 | def down 24 | run!('down') 25 | end 26 | 27 | private 28 | 29 | def docker_compose_legacy_binary_exists? 30 | system('which docker-compose > /dev/null 2>&1') 31 | end 32 | 33 | 34 | def run!(*args) 35 | # file_args and args should be Array of String 36 | file_args = @files.map { |file| "--file=#{file}" } 37 | 38 | if docker_compose_legacy_binary_exists? 39 | command = 'docker-compose' 40 | command_args = file_args + args 41 | else 42 | command = 'docker' 43 | command_args = ['compose'] + file_args + args 44 | end 45 | 46 | @last_command = Command.run(command, *command_args, dir: @dir).join 47 | status = @last_command.status 48 | out = @last_command.captured_output 49 | err = @last_command.captured_error 50 | unless status.success? 51 | desc = (out + err).strip.lines.first || '(no output)' 52 | message = format("'%s' failed with status %s: %s", args.first, status.to_s, desc) 53 | raise message 54 | end 55 | 56 | out 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/docker-sync/environment.rb: -------------------------------------------------------------------------------- 1 | require 'os' 2 | 3 | module DockerSync 4 | module Environment 5 | def self.linux? 6 | return @linux if defined? @linux 7 | 8 | @linux = OS.linux? 9 | end 10 | 11 | def self.mac? 12 | return @mac if defined? @mac 13 | 14 | @mac = OS.mac? 15 | end 16 | 17 | def self.freebsd? 18 | @freebsd ||= OS.freebsd? 19 | end 20 | 21 | def self.system(cmd) 22 | defined?(Bundler) ? Bundler.unbundled_system(cmd) : Kernel.system(cmd) 23 | end 24 | 25 | def self.default_ignores() 26 | ['.docker-sync/daemon.log', '.docker-sync/daemon.pid'] 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/docker-sync/execution.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | require 'thor/shell' 3 | 4 | module Execution 5 | Thread.abort_on_exception = true 6 | 7 | def thread_exec(command, prefix = 'unknown', color = :cyan) 8 | Thread.new do 9 | Open3.popen3(command) do |_, stdout, stderr, _| 10 | # noinspection RubyAssignmentExpressionInConditionalInspection 11 | while line_out = stdout.gets 12 | say_status with_time(prefix), line_out, color 13 | end 14 | 15 | # noinspection RubyAssignmentExpressionInConditionalInspection 16 | while line_err = stderr.gets 17 | say_status with_time(prefix), line_err, :red 18 | end 19 | end 20 | end 21 | end 22 | 23 | # unison doesn't work when ran in a new thread 24 | # this functions creates a full new process instead 25 | def fork_exec(command, _prefix = 'unknown', _color = :cyan) 26 | Process.fork { `#{command}` || raise(command + ' failed') } 27 | end 28 | 29 | def with_time(prefix) 30 | "[#{Time.now}] #{prefix}" 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/docker-sync/sync_manager.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell' 2 | # noinspection RubyResolve 3 | require 'docker-sync/sync_process' 4 | # noinspection RubyResolve 5 | require 'docker-sync/execution' 6 | 7 | module DockerSync 8 | class SyncManager 9 | include Thor::Shell 10 | 11 | @sync_processes 12 | @configurations 13 | @config_path 14 | 15 | def initialize(options) 16 | @sync_processes = [] 17 | 18 | load_configuration(options) 19 | end 20 | 21 | def global_options 22 | return @config_global 23 | end 24 | 25 | def get_sync_points 26 | return @config_syncs 27 | end 28 | 29 | def upgrade_syncs_config 30 | @config_syncs.each do |name, config| 31 | @config_syncs[name]['config_path'] = @config_path 32 | 33 | @config_syncs[name]['cli_mode'] = @config_global['cli_mode'] || 'auto' 34 | 35 | # set the global verbose setting, if the sync-endpoint does not define a own one 36 | unless config.key?('verbose') 37 | @config_syncs[name]['verbose'] = false 38 | if @config_global.key?('verbose') 39 | @config_syncs[name]['verbose'] = @config_global['verbose'] 40 | end 41 | end 42 | 43 | # set the global max_attempt setting, if the sync-endpoint does not define a own one 44 | unless config.key?('max_attempt') 45 | if @config_global.key?('max_attempt') 46 | @config_syncs[name]['max_attempt'] = @config_global['max_attempt'] 47 | end 48 | end 49 | 50 | if @config_syncs[name].key?('dest') 51 | puts 'Please do no longer use "dest" in your docker-sync.yml configuration - also see https://docker-sync.readthedocs.io/en/latest/getting-started/upgrade.html#id4' 52 | exit 1 53 | else 54 | @config_syncs[name]['dest'] = '/app_sync' 55 | end 56 | 57 | # for each strategy check if a custom image has been defined and inject that into the sync-endpoints 58 | # which do fit for this strategy 59 | %w(rsync unison native_osx native).each do |strategy| 60 | if @config_global.key?("#{strategy}_image") 61 | @config_syncs[name]['image'] = @config_global["#{strategy}_image"] 62 | end 63 | 64 | if config.key?("#{strategy}_image") && @config_syncs[name]['sync_strategy'] == strategy 65 | @config_syncs[name]['image'] = config["#{strategy}_image"] 66 | end 67 | end 68 | end 69 | end 70 | 71 | def init_sync_processes(sync_name = nil) 72 | return if @sync_processes.size != 0 73 | if sync_name.nil? 74 | @config_syncs.each { |name, sync_configuration| 75 | @sync_processes.push(create_sync(name, sync_configuration)) 76 | } 77 | else 78 | unless @config_syncs.key?(sync_name) 79 | raise("Could not find sync configuration with name #{sync_name}") 80 | end 81 | @sync_processes.push(create_sync(sync_name, @config_syncs[sync_name])) 82 | end 83 | end 84 | 85 | 86 | def clean(sync_name = nil) 87 | init_sync_processes(sync_name) 88 | @sync_processes.each { |sync_process| 89 | sync_process.clean 90 | } 91 | end 92 | 93 | def sync(sync_name = nil) 94 | init_sync_processes(sync_name) 95 | @sync_processes.each { |sync_process| 96 | sync_process.sync 97 | } 98 | end 99 | 100 | def start_container(sync_name = nil) 101 | init_sync_processes(sync_name) 102 | @sync_processes.each { |sync_process| 103 | sync_process.start_container 104 | } 105 | end 106 | 107 | def run(sync_name = nil) 108 | init_sync_processes(sync_name) 109 | 110 | @sync_processes.each { |sync_process| 111 | sync_process.run 112 | } 113 | end 114 | 115 | def join_threads 116 | begin 117 | @sync_processes.each do |sync_process| 118 | if sync_process.watch_thread 119 | sync_process.watch_thread.join 120 | end 121 | if sync_process.watch_fork 122 | Process.wait(sync_process.watch_fork) 123 | end 124 | end 125 | 126 | rescue SystemExit, Interrupt 127 | say_status 'shutdown', 'Shutting down...', :blue 128 | @sync_processes.each do |sync_process| 129 | sync_process.stop 130 | end 131 | 132 | rescue StandardError => e 133 | puts "EXCEPTION: #{e.inspect}" 134 | puts "MESSAGE: #{e.message}" 135 | end 136 | end 137 | 138 | def create_sync(sync_name, sync_configuration) 139 | sync_process = DockerSync::SyncProcess.new(sync_name, sync_configuration) 140 | return sync_process 141 | end 142 | 143 | def stop 144 | init_sync_processes 145 | @sync_processes.each { |sync_process| 146 | sync_process.stop 147 | unless sync_process.watch_thread.nil? 148 | sync_process.watch_thread.kill unless sync_process.watch_thread.nil? 149 | end 150 | } 151 | end 152 | 153 | def watch_stop 154 | @sync_processes.each { |sync_process| 155 | sync_process.watch_thread.kill unless sync_process.watch_thread.nil? 156 | } 157 | end 158 | 159 | def watch_start 160 | @sync_processes.each { |sync_process| 161 | sync_process.watch 162 | } 163 | end 164 | 165 | private 166 | 167 | def load_configuration(options) 168 | config = options[:config] || 169 | DockerSync::ProjectConfig.new( 170 | config_path: options[:config_path], 171 | config_string: options[:config_string] 172 | ) 173 | 174 | @config_path = config.config_path 175 | @config_global = config['options'] || {} 176 | @config_syncs = config['syncs'] 177 | upgrade_syncs_config 178 | end 179 | 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /lib/docker-sync/sync_process.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell' 2 | # noinspection RubyResolve 3 | require 'docker-sync/sync_strategy/rsync' 4 | require 'docker-sync/sync_strategy/unison' 5 | require 'docker-sync/sync_strategy/native' 6 | require 'docker-sync/sync_strategy/native_osx' 7 | 8 | # noinspection RubyResolve 9 | require 'docker-sync/watch_strategy/fswatch' 10 | require 'docker-sync/watch_strategy/dummy' 11 | require 'docker-sync/watch_strategy/unison' 12 | require 'docker-sync/watch_strategy/remotelogs' 13 | 14 | module DockerSync 15 | class SyncProcess 16 | include Thor::Shell 17 | @options 18 | @sync_name 19 | @watch_thread 20 | @sync_strategy 21 | @watch_strategy 22 | 23 | # noinspection RubyStringKeysInHashInspection 24 | def initialize(sync_name, options) 25 | @sync_name = sync_name 26 | 27 | defaults = { 28 | 'verbose' => false, 29 | } 30 | 31 | # even if sync_host_ip is set, if it is set to auto, enforce the default 32 | if !options.key?('sync_host_ip') || options['sync_host_ip'] == 'auto' || options['sync_host_ip'] == '' 33 | options['sync_host_ip'] = get_host_ip_default 34 | end 35 | 36 | @options = defaults.merge(options) 37 | @sync_strategy = nil 38 | @watch_strategy = nil 39 | set_sync_strategy 40 | set_watch_strategy 41 | end 42 | 43 | def set_sync_strategy 44 | case @options['sync_strategy'] 45 | when 'rsync' 46 | @sync_strategy = DockerSync::SyncStrategy::Rsync.new(@sync_name, @options) 47 | when 'unison' 48 | @sync_strategy = DockerSync::SyncStrategy::Unison.new(@sync_name, @options) 49 | when 'native' 50 | @sync_strategy = DockerSync::SyncStrategy::Native.new(@sync_name, @options) 51 | when 'native_osx' 52 | @sync_strategy = DockerSync::SyncStrategy::NativeOsx.new(@sync_name, @options) 53 | else 54 | raise "Unknown sync_strategy #{@options['sync_strategy']}" 55 | end 56 | end 57 | 58 | def set_watch_strategy 59 | case @options['watch_strategy'] 60 | when 'fswatch' 61 | @watch_strategy = DockerSync::WatchStrategy::Fswatch.new(@sync_name, @options) 62 | when 'dummy' 63 | @watch_strategy = DockerSync::WatchStrategy::Dummy.new(@sync_name, @options) 64 | when 'unison' 65 | @watch_strategy = DockerSync::WatchStrategy::Unison.new(@sync_name, @options) 66 | when 'remotelogs' 67 | @watch_strategy = DockerSync::WatchStrategy::Remote_logs.new(@sync_name, @options) 68 | else 69 | raise "Unknown watch_strategy #{@options['watch_strategy']}" 70 | end 71 | end 72 | 73 | def get_host_ip_default 74 | return '127.0.0.1' unless Dependencies::Docker::Driver.docker_toolbox? 75 | 76 | cmd = 'docker-machine ip $(docker-machine active)' 77 | stdout, stderr, exit_status = Open3.capture3(cmd) 78 | unless exit_status.success? 79 | raise "Error getting sync_host_ip automatically, exit code #{$?.exitstatus} ... #{stderr}" 80 | end 81 | stdout.gsub("\n",'') 82 | end 83 | 84 | def run 85 | @sync_strategy.run 86 | @watch_strategy.run 87 | end 88 | 89 | def stop 90 | @sync_strategy.stop 91 | @watch_strategy.stop 92 | end 93 | 94 | def clean 95 | @sync_strategy.clean 96 | @watch_strategy.clean 97 | end 98 | 99 | def sync 100 | # TODO: probably use run here 101 | @sync_strategy.sync 102 | end 103 | 104 | def start_container 105 | @sync_strategy.start_container 106 | end 107 | 108 | def watch 109 | @watch_strategy.run 110 | end 111 | 112 | def watch_fork 113 | return @watch_strategy.watch_fork 114 | end 115 | 116 | def watch_thread 117 | return @watch_strategy.watch_thread 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/docker-sync/sync_strategy/native.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell' 2 | 3 | module DockerSync 4 | module SyncStrategy 5 | class Native 6 | include Thor::Shell 7 | 8 | @options 9 | @sync_name 10 | 11 | def initialize(sync_name, options) 12 | @sync_name = sync_name 13 | @options = options 14 | 15 | begin 16 | Dependencies::Docker.ensure! 17 | rescue StandardError => e 18 | say_status 'error', "#{@sync_name} has been configured to sync with native docker volume, but docker is not found", :red 19 | say_status 'error', e.message, :red 20 | exit 1 21 | end 22 | end 23 | 24 | def run 25 | create_volume 26 | end 27 | 28 | def sync 29 | # noop 30 | end 31 | 32 | def get_volume_name 33 | @sync_name 34 | end 35 | 36 | def start_container 37 | # noop 38 | end 39 | 40 | def clean 41 | delete_volume 42 | end 43 | 44 | def stop 45 | # noop 46 | end 47 | 48 | private 49 | 50 | def create_volume 51 | run_cmd "docker volume create --opt type=none --opt device=\"#{@options['src']}\" --opt o=bind "\ 52 | "--name #{get_volume_name}" 53 | 54 | say_status 'success', "Docker volume for #{get_volume_name} created", :white 55 | end 56 | 57 | def delete_volume 58 | run_cmd "docker volume ls -q | grep #{get_volume_name} && docker volume rm #{get_volume_name}" 59 | end 60 | 61 | def run_cmd(cmd) 62 | say_status 'command', cmd, :white if @options['verbose'] 63 | 64 | `#{cmd}` 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/docker-sync/update_check.rb: -------------------------------------------------------------------------------- 1 | require 'gem_update_checker' 2 | require 'thor/actions' 3 | require 'docker-sync/config/global_config' 4 | 5 | class UpdateChecker 6 | include Thor::Shell 7 | 8 | def initialize 9 | @config = DockerSync::GlobalConfig.load 10 | @newer_image_found = false 11 | end 12 | 13 | def run 14 | return if ENV['DOCKER_SYNC_SKIP_UPDATE'] 15 | unless @config['update_check'] 16 | say_status 'hint','Skipping up-to-date check since it has been disabled in your ~/.docker-sync-global.yml configuration',:yellow 17 | return 18 | end 19 | unless should_run 20 | return 21 | end 22 | 23 | # do not check the image if its the first run - since this it will be downloaded anyway 24 | unless @config.first_run? 25 | unless has_internet? 26 | check_rsync_image 27 | # stop if there was an update 28 | if @newer_image_found 29 | say_status 'warning', 'One or more images have been updated. Please use "docker-sync clean" before you start docker-sync again', :red 30 | exit 0 31 | end 32 | end 33 | end 34 | 35 | check_and_warn(@config['update_enforce']) 36 | end 37 | 38 | def has_internet? 39 | `ping -c1 -t 1 8.8.8.8 > /dev/null 2>&1` 40 | return $?.success? 41 | end 42 | 43 | def should_run 44 | return false unless has_internet? 45 | now = DateTime.now 46 | return true if @config['update_last_check'].nil? 47 | 48 | last_check = DateTime.iso8601(@config['update_last_check']) 49 | check_after_days = 2 50 | if now - last_check > check_after_days 51 | return true 52 | end 53 | 54 | return false 55 | end 56 | 57 | def check_rsync_image 58 | return if ENV['DOCKER_SYNC_SKIP_UPDATE'] 59 | say_status 'ok','Checking if a newer rsync image is available' 60 | 61 | if system("docker pull eugenmayer/rsync | grep 'Downloaded newer image for'") 62 | say_status 'ok', 'Downloaded newer image for rsync', :green 63 | @newer_image_found = true 64 | else 65 | say_status 'ok', 'No newer image found - current image is up to date.' 66 | end 67 | 68 | end 69 | 70 | def get_current_version 71 | path = File.expand_path('../../../', __FILE__) 72 | version = File.read("#{path}/VERSION") 73 | version.strip 74 | version 75 | end 76 | 77 | def docker_sync_update_check 78 | gem_name = 'docker-sync' 79 | current_version = get_current_version 80 | checker = GemUpdateChecker::Client.new(gem_name, current_version) 81 | return checker 82 | end 83 | 84 | def check_and_warn(update_enforced = true) 85 | return if ENV['DOCKER_SYNC_SKIP_UPDATE'] 86 | # update the timestamp 87 | @config.update! 'update_last_check' => DateTime.now.iso8601(9) 88 | 89 | check = docker_sync_update_check 90 | if check.update_available 91 | say_status 'warning',"There is an update (#{check.latest_version}) available (current version #{check.current_version}). Please update before you continue",:yellow 92 | if yes?("Shall I update docker-sync to #{check.latest_version} for you?") 93 | if system('gem update docker-sync') 94 | say_status 'success','Successfully updated, please restart docker-sync and check the changelog at https://github.com/EugenMayer/docker-sync/releases',:green 95 | exit 0 96 | else 97 | say_status 'error','Unable to update docker-sync',:red 98 | exit 1 99 | end 100 | else 101 | exit 1 if update_enforced 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/docker-sync/upgrade_check.rb: -------------------------------------------------------------------------------- 1 | require 'gem_update_checker' 2 | require 'thor/actions' 3 | require 'docker-sync/config/global_config' 4 | require 'docker-sync/update_check' 5 | 6 | class UpgradeChecker 7 | include Thor::Shell 8 | def initialize 9 | @config = DockerSync::GlobalConfig.load 10 | end 11 | 12 | def run 13 | return if ENV['DOCKER_SYNC_SKIP_UPGRADE'] 14 | unless should_run 15 | return 16 | end 17 | check_and_warn 18 | end 19 | 20 | def last_upgraded_version 21 | @config['upgrade_status'] 22 | end 23 | 24 | def should_run 25 | # get the update_status which is the version of the update hook which has been run already 26 | upgrade_status = last_upgraded_version 27 | if upgrade_status == '' 28 | @config.update! 'upgrade_status' => "#{UpgradeChecker.get_current_version}" 29 | return 30 | end 31 | 32 | if Gem::Version.new(upgrade_status) < Gem::Version.new(UpgradeChecker.get_current_version) # thats how we compare the version 33 | return true 34 | end 35 | 36 | return false 37 | end 38 | 39 | 40 | def self.get_current_version 41 | path = File.expand_path('../../../', __FILE__) 42 | version = File.read("#{path}/VERSION") 43 | version.strip 44 | version 45 | end 46 | 47 | def docker_sync_update_check 48 | gem_name = 'docker-sync' 49 | current_version = UpgradeChecker.get_current_version 50 | checker = GemUpdateChecker::Client.new(gem_name, current_version) 51 | return checker 52 | end 53 | 54 | def check_and_warn 55 | return if ENV['DOCKER_SYNC_SKIP_UPGRADE'] 56 | 57 | if Gem::Version.new(last_upgraded_version) < Gem::Version.new('0.5.6') 58 | Thor::Shell::Basic.new.say_status 'warning', "If you are upgrading from 0.5.4 or below, please run `brew update && brew upgrade unison` AND `docker-compose down && docker-sync clean` or `docker-sync-stack clean` since you need to recreate the sync container", :red 59 | 60 | unless Thor::Shell::Basic.new.yes?('Sync will fail otherwise. Continue? (y/N)') 61 | exit 1 62 | end 63 | end 64 | 65 | if Gem::Version.new(last_upgraded_version) < Gem::Version.new('0.5.12') 66 | Thor::Shell::Basic.new.say_status 'warning', "0.5.12 uses a newer OCALM 4.08.1 version in the Unison image. If you use the unison strategy, please upgrade your local unison to be compiled against OCALM 4.08.1", :red 67 | 68 | unless Thor::Shell::Basic.new.yes?('Sync will fail otherwise. Continue? (y/N)') 69 | exit 1 70 | end 71 | end 72 | 73 | # update the upgrade_status 74 | @config.update! 'upgrade_status' => "#{UpgradeChecker.get_current_version}" 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/docker-sync/watch_strategy/dummy.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell' 2 | require 'docker-sync/execution' 3 | 4 | module DockerSync 5 | module WatchStrategy 6 | class Dummy 7 | include Thor::Shell 8 | include Execution 9 | 10 | @options 11 | @sync_name 12 | @watch_fork 13 | @watch_thread 14 | 15 | def initialize(sync_name, options) 16 | @options = options 17 | @sync_name = sync_name 18 | @watch_fork = nil 19 | @watch_thread = nil 20 | end 21 | 22 | def run 23 | say_status 'success', 'Watcher disabled by configuration' if @options['verbose'] 24 | end 25 | 26 | def stop 27 | end 28 | 29 | def clean 30 | end 31 | 32 | def watch 33 | end 34 | 35 | def watch_options 36 | end 37 | 38 | def watch_fork 39 | return @watch_fork 40 | end 41 | 42 | def watch_thread 43 | return @watch_thread 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/docker-sync/watch_strategy/fswatch.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell' 2 | require 'docker-sync/execution' 3 | require 'pathname' 4 | 5 | module DockerSync 6 | module WatchStrategy 7 | class Fswatch 8 | include Thor::Shell 9 | include Execution 10 | 11 | @options 12 | @sync_name 13 | @watch_thread 14 | 15 | def initialize(sync_name, options) 16 | @sync_name = sync_name 17 | @options = options 18 | @events_to_watch = %w(AttributeModified Created Link MovedFrom MovedTo Renamed Removed Updated) 19 | 20 | unless Dependencies::Fswatch.available? 21 | begin 22 | Dependencies::Fswatch.ensure! 23 | rescue StandardError => e 24 | say_status 'error', e.message, :red 25 | exit 1 26 | end 27 | puts "please restart docker sync so the installation of fswatch takes effect" 28 | raise(UNSUPPORTED_OPERATING_SYSTEM) 29 | end 30 | 31 | end 32 | 33 | def run 34 | watch 35 | end 36 | 37 | def stop 38 | end 39 | 40 | def clean 41 | end 42 | 43 | def watch 44 | args = watch_options 45 | say_status 'success', "Starting to watch #{@options['src']} - Press CTRL-C to stop", :green 46 | cmd = 'fswatch ' + args.join(' ') 47 | say_status 'command', cmd, :white if @options['verbose'] 48 | 49 | # run a thread here, since it is blocking 50 | @watch_thread = thread_exec(cmd, "Sync #{@sync_name}", :blue) 51 | end 52 | 53 | def watch_options 54 | args = [] 55 | unless @options['watch_excludes'].nil? 56 | args = @options['watch_excludes'].map { |pattern| "--exclude='#{pattern}'" } + args 57 | end 58 | args.push('-orIE') 59 | args.push(@events_to_watch.map { |pattern| "--event #{pattern}" }) 60 | args.push(@options['watch_args']) if @options.key?('watch_args') 61 | args.push("'#{@options['src']}'") 62 | sync_command = get_sync_cli_call 63 | args.push(" | xargs -I -n1 #{sync_command} -n #{@sync_name} --config='#{@options['config_path']}'") 64 | end 65 | 66 | def get_sync_cli_call 67 | sync_command = 'thor sync:' 68 | case @options['cli_mode'] 69 | when 'docker-sync' 70 | say_status 'ok','Forcing cli mode docker-sync',:yellow if @options['verbose'] 71 | sync_command = 'docker-sync ' 72 | when 'thor' 73 | say_status 'ok','Forcing cli mode thor',:yellow if @options['verbose'] 74 | sync_command = 'thor sync:' 75 | else # 'auto' or any other, try to guss 76 | say_status 'ok','Cli mode is auto, selecting .. ',:white if @options['verbose'] 77 | exec_name = File.basename($PROGRAM_NAME) 78 | if exec_name != 'thor' 79 | sync_command = 'docker-sync ' 80 | else 81 | say_status 'warning', 'Called user thor, not docker-sync* wise, assuming dev mode, using thor', :yellow 82 | end 83 | say_status 'ok',".. #{sync_command}",:white if @options['verbose'] 84 | end 85 | 86 | # append the actual operation 87 | return "#{sync_command}sync" 88 | end 89 | 90 | def watch_fork 91 | return nil 92 | end 93 | 94 | def watch_thread 95 | return @watch_thread 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/docker-sync/watch_strategy/remotelogs.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell' 2 | require 'docker-sync/execution' 3 | 4 | module DockerSync 5 | module WatchStrategy 6 | class Remote_logs 7 | include Thor::Shell 8 | include Execution 9 | 10 | @options 11 | @sync_name 12 | @watch_fork 13 | @watch_thread 14 | 15 | def initialize(sync_name, options) 16 | @options = options 17 | @sync_name = sync_name 18 | @watch_fork = nil 19 | @watch_thread = nil 20 | @unison = DockerSync::SyncStrategy::NativeOsx.new(@sync_name, @options) 21 | end 22 | 23 | def run 24 | say_status 'success', "Showing unison logs from your sync container: #{@unison.get_container_name}", :green 25 | cmd = "docker exec #{@unison.get_container_name} tail -F /tmp/unison.log" 26 | @watch_thread = thread_exec(cmd, 'Sync Log:') 27 | end 28 | 29 | def stop 30 | end 31 | 32 | def clean 33 | end 34 | 35 | def watch 36 | end 37 | 38 | def watch_options 39 | end 40 | 41 | def watch_fork 42 | return @watch_fork 43 | end 44 | 45 | def watch_thread 46 | return @watch_thread 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/docker-sync/watch_strategy/unison.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell' 2 | require 'docker-sync/execution' 3 | require 'docker-sync/sync_strategy/unison' 4 | 5 | module DockerSync 6 | module WatchStrategy 7 | class Unison 8 | include Execution 9 | 10 | @options 11 | @sync_name 12 | @watch_fork 13 | 14 | def initialize(sync_name, options) 15 | @options = options 16 | @sync_name = sync_name 17 | @watch_fork = nil 18 | # instantiate the sync task to easily access all common parameters between 19 | # unison sync and watch 20 | # basically unison watch is the command with the additionnal -repeat watch option 21 | # note: this doesn't run a sync 22 | @unison = DockerSync::SyncStrategy::Unison.new(@sync_name, @options) 23 | end 24 | 25 | def run 26 | @watch_fork = @unison.watch 27 | end 28 | 29 | def stop 30 | # Make sure @watch_fork is not nil otherwise a TypeError is thrown 31 | if @watch_fork 32 | Process.kill 'TERM', @watch_fork 33 | Process.wait @watch_fork 34 | end 35 | end 36 | 37 | def clean 38 | end 39 | 40 | def watch 41 | end 42 | 43 | def watch_options 44 | end 45 | 46 | def watch_fork 47 | return @watch_fork 48 | end 49 | 50 | def watch_thread 51 | return nil 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | gem build docker-sync.gemspec 3 | version=`cat VERSION` 4 | gem push docker-sync-$version.gem 5 | rm docker-sync-$version.gem 6 | git tag $version 7 | git push 8 | git push --tags 9 | -------------------------------------------------------------------------------- /spec/fixtures/app_skeleton/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CSRC=src/main.c 3 | CBIN=super-useful-program 4 | 5 | $(CBIN): $(CSRC) 6 | $(CC) $(CFLAGS) -o $(CBIN) $(CSRC) 7 | 8 | clean: 9 | rm -f $(CBIN) 10 | -------------------------------------------------------------------------------- /spec/fixtures/app_skeleton/README.md: -------------------------------------------------------------------------------- 1 | # Super useful program 2 | 3 | It's so useful :astonished: 4 | -------------------------------------------------------------------------------- /spec/fixtures/app_skeleton/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | printf("Wow! So useful!\n"); 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/dummy/docker-sync.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/EugenMayer/docker-sync-boilerplate/blob/master/rsync/docker-sync.yml 2 | 3 | version: "2" 4 | 5 | syncs: 6 | #IMPORTANT: ensure this name is unique and does not match your other application container name 7 | appcode-dummy-sync: #tip: add -sync and you keep consistent names als a convention 8 | src: './app' 9 | dest: '/var/www' 10 | watch_strategy: 'dummy' 11 | -------------------------------------------------------------------------------- /spec/fixtures/dynamic-configuration-dotenv/.env: -------------------------------------------------------------------------------- 1 | SYNC_NAME=docker-boilerplate 2 | 3 | # Application's path (absolute or relative) 4 | APP_PATH=./app 5 | 6 | # Path inside of container where the APP_PATH will be mounted, 7 | # This var can also be used as workdir value for docker 8 | DESTINATION_PATH=/var/www 9 | -------------------------------------------------------------------------------- /spec/fixtures/dynamic-configuration-dotenv/docker-sync.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/EugenMayer/docker-sync-boilerplate/blob/master/dynamic-configuration-dotnev/docker-sync.yml 2 | 3 | version: "2" 4 | 5 | options: 6 | verbose: true 7 | syncs: 8 | #IMPORTANT: ensure this name is unique and does not match your other application container name 9 | ${SYNC_NAME}-unison-sync: # tip: add -sync and you keep consistent names als a convention 10 | src: '${APP_PATH}' 11 | dest: '${DESTINATION_PATH}' 12 | sync_excludes: ['ignored_folder', '.ignored_dot_folder'] 13 | -------------------------------------------------------------------------------- /spec/fixtures/mismatch_version/docker-sync.yml: -------------------------------------------------------------------------------- 1 | version: '1' 2 | 3 | syncs: 4 | #IMPORTANT: ensure this name is unique and does not match your other application container name 5 | mismatch-version-sync: #tip: add -sync and you keep consistent names als a convention 6 | src: './app' 7 | dest: '/var/www' 8 | -------------------------------------------------------------------------------- /spec/fixtures/missing_syncs/docker-sync.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/EugenMayer/docker-sync-boilerplate/blob/master/simplest/docker-sync.yml 2 | 3 | version: "2" 4 | -------------------------------------------------------------------------------- /spec/fixtures/missing_syncs_src/docker-sync.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | syncs: 4 | #IMPORTANT: ensure this name is unique and does not match your other application container name 5 | missing-syncs-src-sync: #tip: add -sync and you keep consistent names als a convention 6 | dest: '/var/www' 7 | -------------------------------------------------------------------------------- /spec/fixtures/missing_syncs_sync_host_port/docker-sync.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | syncs: 4 | #IMPORTANT: ensure this name is unique and does not match your other application container name 5 | missing-syncs-sync_host_port-sync: #tip: add -sync and you keep consistent names als a convention 6 | src: './app' 7 | dest: '/var/www' 8 | sync_strategy: 'rsync' 9 | -------------------------------------------------------------------------------- /spec/fixtures/missing_version/docker-sync.yml: -------------------------------------------------------------------------------- 1 | syncs: 2 | #IMPORTANT: ensure this name is unique and does not match your other application container name 3 | missing-version-sync: #tip: add -sync and you keep consistent names als a convention 4 | src: './app' 5 | dest: '/var/www' 6 | -------------------------------------------------------------------------------- /spec/fixtures/native_osx/docker-sync.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | options: 3 | verbose: true 4 | syncs: 5 | docker_sync_specs-sync: 6 | src: {{HOST_APP_PATH}} 7 | sync_strategy: native_osx 8 | -------------------------------------------------------------------------------- /spec/fixtures/project_root/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugenMayer/docker-sync/fda21cf1b3bb6f3f94322651eb2375979974c255/spec/fixtures/project_root/app/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/project_root/docker-sync.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | options: 4 | project_root: config_path 5 | 6 | syncs: 7 | #IMPORTANT: ensure this name is unique and does not match your other application container name 8 | project_root-sync: #tip: add -sync and you keep consistent names als a convention 9 | src: './app' 10 | dest: '/var/www' 11 | -------------------------------------------------------------------------------- /spec/fixtures/rsync/docker-sync.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/EugenMayer/docker-sync-boilerplate/blob/master/rsync/docker-sync.yml 2 | 3 | version: "2" 4 | options: 5 | verbose: true 6 | syncs: 7 | #IMPORTANT: ensure this name is unique and does not match your other application container name 8 | appcode-rsync-sync: #tip: add -sync and you keep consistent names als a convention 9 | src: './app' 10 | dest: '/var/www' 11 | sync_host_ip: 'localhost' 12 | sync_host_port: 10872 13 | sync_strategy: 'rsync' 14 | -------------------------------------------------------------------------------- /spec/fixtures/simplest/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugenMayer/docker-sync/fda21cf1b3bb6f3f94322651eb2375979974c255/spec/fixtures/simplest/app/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/simplest/docker-sync.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/EugenMayer/docker-sync-boilerplate/blob/master/simplest/docker-sync.yml 2 | 3 | version: "2" 4 | 5 | syncs: 6 | #IMPORTANT: ensure this name is unique and does not match your other application container name 7 | simplest-sync: #tip: add -sync and you keep consistent names als a convention 8 | src: './app' 9 | dest: '/var/www' 10 | -------------------------------------------------------------------------------- /spec/fixtures/unison/docker-sync.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/EugenMayer/docker-sync-boilerplate/blob/master/unison/docker-sync.yml 2 | 3 | version: "2" 4 | 5 | options: 6 | verbose: true 7 | syncs: 8 | #IMPORTANT: ensure this name is unique and does not match your other application container name 9 | appcode-unison-sync: # tip: add -sync and you keep consistent names als a convention 10 | src: './app' 11 | dest: '/var/www' 12 | sync_excludes: ['ignored_folder', '.ignored_dot_folder'] 13 | sync_strategy: 'unison' 14 | -------------------------------------------------------------------------------- /spec/helpers/command_mocking_helpers.rb: -------------------------------------------------------------------------------- 1 | module CommandExecutionMock 2 | COMMAND_EXECUTORS = %i(system exec exit spawn `).freeze 3 | 4 | def self.included(_base) 5 | define_custom_matchers 6 | disallow_command_execution 7 | end 8 | 9 | def define_custom_matchers 10 | RSpec::Matchers.define :execute_nothing do 11 | match do 12 | COMMAND_EXECUTORS.each do |command_executor| 13 | expect(Kernel).to_not receive(command_executor) 14 | end 15 | end 16 | end 17 | end 18 | 19 | def disallow_command_execution 20 | print "Adding RSpec hook to stub command execution... " 21 | RSpec.configuration.before(:each) do |example| 22 | unless example.metadata[:command_execution].to_s == 'allowed' 23 | COMMAND_EXECUTORS.each do |executor| 24 | stub_command_executor(executor) 25 | end 26 | end 27 | end 28 | puts "OK" 29 | end 30 | 31 | def stub_command_executor(executor) 32 | allow_any_instance_of(Object).to receive(executor).and_wrap_original do |method, *args| 33 | raise CommandExecutionNotAllowed.new(method.name, *args) 34 | end 35 | allow(Kernel).to receive(executor).and_wrap_original do |method, *args| 36 | raise CommandExecutionNotAllowed.new(method.name, *args) 37 | end 38 | end 39 | 40 | class CommandExecutionNotAllowed < ::StandardError 41 | attr_reader :method_name, :args 42 | def initialize(method_name, *args) 43 | @method_name = method_name 44 | @args = args 45 | end 46 | 47 | def to_s 48 | "Command execution is not allowed. Please stub the following call: #{method_name}(#{args.map(&:inspect).join(', ')})" 49 | end 50 | end 51 | end 52 | 53 | RSpec.configure do 54 | include CommandExecutionMock 55 | end 56 | -------------------------------------------------------------------------------- /spec/helpers/docker_for_mac_config_check.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module DockerForMacConfigCheck 4 | D4M_MOUNTS_FILE = File.expand_path('~/Library/Containers/com.docker.docker/Data/database/com.docker.driver.amd64-linux/mounts').freeze 5 | WARNING_SIGN = "\033[0;33mWARNING\033[0m ".freeze 6 | ANSI_COLOR_CHAR_REGEX = /\033\[(\d{1,3}(;\d{1,3})?)?m/ 7 | 8 | def self.included(_base) 9 | return unless File.exist?(D4M_MOUNTS_FILE) 10 | File.readlines(D4M_MOUNTS_FILE).each do |mount_line| 11 | mount_src, _mount_dst = mount_line.split(':') 12 | Pathname.new(File.realpath(Dir.tmpdir)).ascend do |parent_dir| 13 | return if File.realpath(parent_dir) == File.realpath(mount_src) 14 | end 15 | end 16 | big_warning(<<-EOS) 17 | The OS temporary directory (#{File.realpath(Dir.tmpdir)}) does not seem to be shared with Hyperkit VM. 18 | The integration tests will likely fail (or at least, behave unexpectedly). 19 | Please go to Docker -> Preferences -> File Sharing tab and add it (or a parent directory). 20 | EOS 21 | end 22 | 23 | def self.tmpdir_toplevel_dir 24 | @tmpdir_toplevel_dir ||= Dir.tmpdir.gsub(/(\/[^\/]+).*/, '\\1') 25 | end 26 | 27 | def self.big_warning(message) 28 | message.gsub!(/(^\s*|\s*$)/, '') 29 | message_length = message.lines.max_by(&:length).length 30 | warning_length = WARNING_SIGN.gsub(ANSI_COLOR_CHAR_REGEX, '').length 31 | total_length = warning_length * (message_length / warning_length + 1) 32 | puts "#{WARNING_SIGN}#{WARNING_SIGN * (total_length / warning_length)}#{WARNING_SIGN}" 33 | message.each_line do |line| 34 | puts "#{WARNING_SIGN}#{line.strip.center(total_length)}#{WARNING_SIGN}" 35 | end 36 | puts "#{WARNING_SIGN}#{WARNING_SIGN * (total_length / warning_length)}#{WARNING_SIGN}" 37 | end 38 | end 39 | 40 | RSpec.configure do |config| 41 | include DockerForMacConfigCheck 42 | end 43 | -------------------------------------------------------------------------------- /spec/helpers/fixture_helpers.rb: -------------------------------------------------------------------------------- 1 | module FixtureHelpers 2 | def fixture_path(name) 3 | File.expand_path File.join(__dir__, '..', 'fixtures', name) 4 | end 5 | 6 | def use_fixture(name) 7 | Dir.chdir fixture_path(name) do 8 | yield 9 | end 10 | end 11 | end 12 | 13 | RSpec.configuration.include(FixtureHelpers) 14 | -------------------------------------------------------------------------------- /spec/helpers/raise_error_helpers.rb: -------------------------------------------------------------------------------- 1 | module RaiseErrorHelpers 2 | def is_expected_to_raise_error(*args) 3 | expect { subject }.to raise_error(*args) 4 | end 5 | 6 | def is_expected_not_to_raise_error 7 | expect { subject }.not_to raise_error 8 | end 9 | alias is_expected_to_not_raise_error is_expected_not_to_raise_error 10 | end 11 | 12 | RSpec.configuration.include(RaiseErrorHelpers) 13 | -------------------------------------------------------------------------------- /spec/helpers/replace_in_file.rb: -------------------------------------------------------------------------------- 1 | module ReplaceInFile 2 | def replace_in_file(file_path, pattern, replacement) 3 | file_content = File.read(file_path) 4 | new_file_content = file_content.gsub(pattern, replacement) 5 | File.write(file_path, new_file_content) 6 | end 7 | end 8 | 9 | RSpec.configure do 10 | include ReplaceInFile 11 | end 12 | -------------------------------------------------------------------------------- /spec/helpers/synchronization_helpers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :be_in_sync_with do |container_dir| 2 | attr_reader :actual, :expected, :container_dir, :host_dir 3 | 4 | def container_dir_matches_host_dir? 5 | @actual, host_stderr, _status = env.execute_inline("cd #{host_dir}; find . -type f -print0 | sort -z | xargs -0 shasum -a 256") 6 | @expected, container_stderr, _status = env.execute_inline("docker exec #{container_name} bash -c 'cd #{container_dir}; find . -type f -print0 | sort -z | xargs -0 sha256sum'") 7 | raise host_stderr unless host_stderr.empty? 8 | raise container_stderr unless container_stderr.empty? 9 | expected == actual 10 | end 11 | 12 | match do |host_dir| 13 | @container_dir = container_dir 14 | @host_dir = host_dir 15 | @start_time = Time.now 16 | 17 | while Time.now < @start_time + delay 18 | if container_dir_matches_host_dir? 19 | # puts "Yay: #{diff_target} matches #{diff_source} in #{(Time.now - @start_time).round(3).seconds.inspect} instead of #{delay.inspect}." 20 | break true 21 | end 22 | sleep 0.25.second 23 | end 24 | end 25 | 26 | chain :in_container do |container_name| 27 | @container_name = container_name 28 | end 29 | 30 | chain :within do |delay| 31 | @delay = delay 32 | end 33 | 34 | chain :immediately do 35 | @delay = 0.second 36 | end 37 | 38 | def container_name 39 | @container_name.presence || raise('Container name is missing. Please chain `.in_container(container_name)` to define it.'.freeze) 40 | end 41 | 42 | def delay 43 | @delay ||= 1.second 44 | end 45 | 46 | diffable 47 | description { "synchronize #{diff_target} with #{diff_source} within #{delay.inspect}." } 48 | failure_message { "Expected #{diff_target} to match #{diff_source} within #{delay.inspect}." } 49 | failure_message_when_negated { "Expected #{diff_target} to be different than #{diff_source} within #{delay.inspect}." } 50 | 51 | def diff_source 52 | "`#{host_dir}` filetree on host" 53 | end 54 | 55 | def diff_target 56 | "`#{container_dir}` filetree in container `#{container_name}`" 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/integration/native-osx_strategy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fileutils' 3 | require 'os' 4 | require 'rspec/bash' 5 | 6 | RSpec.describe 'native_osx strategy', command_execution: :allowed, if: OS.mac? do 7 | include Rspec::Bash 8 | 9 | let(:env) { create_stubbed_env } 10 | let(:dir) { Dir.tmpdir } 11 | let(:bin) { File.expand_path(File.join(__dir__, '..', '..', 'bin', 'docker-sync')) } 12 | let(:config_file_template) { File.expand_path(File.join(__dir__, '..', '..', 'spec', 'fixtures', 'native_osx', 'docker-sync.yml')) } 13 | let(:config_file_path) { File.join(dir, 'docker-sync.yml') } 14 | let(:host_app_template) { 'spec/fixtures/app_skeleton' } 15 | let(:host_app_path) { File.join(dir, File.basename(host_app_template)) } 16 | let(:test_env_vars) { { 'DOCKER_SYNC_SKIP_UPGRADE' => 'true', 'DOCKER_SYNC_SKIP_UPDATE' => 'true', 'DOCKER_SYNC_SKIP_DEPENDENCIES_CHECK' => 'true' } } 17 | 18 | before :all do 19 | # Cleanup potentially leftover container from previously killed test suite 20 | system('docker rm -f -v docker_sync_specs-sync > /dev/null 2>&1') 21 | end 22 | 23 | describe 'start' do 24 | around(:each) do |example| 25 | FileUtils.cp_r([host_app_template, config_file_template], dir) 26 | FileUtils.chdir(dir) do 27 | replace_in_file(config_file_path, '{{HOST_APP_PATH}}', "'#{host_app_path}'") 28 | _stdout, stderr, status = env.execute_inline("#{bin} start", test_env_vars) 29 | raise stderr unless status.success? 30 | example.run 31 | env.execute_inline("#{bin} clean", test_env_vars) 32 | end 33 | end 34 | 35 | it 'creates a PID file' do 36 | sleep 1 # because, you know ¯\_(ツ)_/¯ 37 | expect(File.file?('.docker-sync/daemon.pid')).to be true 38 | end 39 | 40 | it 'creates a log file' do 41 | expect(File.file?('.docker-sync/daemon.log')).to be true 42 | end 43 | 44 | it_behaves_like 'a synchronized directory', '/host_sync', within: 5.second 45 | it_behaves_like 'a synchronized directory', '/app_sync', within: 10.seconds 46 | 47 | xit "is a manual debugging test" do 48 | # Welcome to a state where you can debug DockerSync synchronization. In another terminal, you can: 49 | # * open unison logs: docker exec docker_sync_specs-sync tail -f /tmp/unison*.log 50 | # * monitor FS events: docker exec docker_sync_specs-sync inotifywait -m -r /host_sync/ 51 | # * go to synced host app: cd #{host_app_path} 52 | # * get inside sync container: docker exec -it docker_sync_specs-sync bash 53 | binding.pry 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/integration/version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'docker-sync/upgrade_check' 3 | require 'rspec/bash' 4 | 5 | RSpec.describe '--version', command_execution: :allowed do 6 | include Rspec::Bash 7 | 8 | let(:env) { create_stubbed_env } 9 | 10 | subject { 'bin/docker-sync --version' } 11 | 12 | it 'outputs the version' do 13 | stdout, _stderr, _status = env.execute_inline(subject) 14 | # puts will always add a newline, so we have to compare against that 15 | expect(stdout).to eq UpgradeChecker.get_current_version 16 | end 17 | 18 | it 'is successful' do 19 | _stdout, _stderr, status = env.execute_inline(subject) 20 | expect(status).to be_success 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'docker-sync/command' 3 | 4 | RSpec.describe DockerSync::Command do 5 | # actual subprocess invocations are mocked out. 6 | describe '.run' do 7 | let(:pid) { 123 } 8 | %i[master slave reader writer].each do |fd| 9 | let(fd) { double(fd, close: true) } 10 | end 11 | 12 | before do 13 | # Use fake PTY to avoid MacOS resource exhaustion 14 | allow(PTY).to receive(:open).and_return([master, slave]) 15 | allow(IO).to receive(:pipe).and_return([reader, writer]) 16 | allow(described_class).to receive(:spawn).and_return(pid) 17 | end 18 | 19 | it 'spawns with new pwd with :dir option' do 20 | expect(described_class).to receive(:spawn).with('ls', hash_including(chdir: '/tmp/banana')) 21 | described_class.run('ls', dir: '/tmp/banana') 22 | end 23 | 24 | it 'spawns with PWD without :dir option' do 25 | expect(described_class).to receive(:spawn).with('ls', hash_including(chdir: Dir.pwd)) 26 | described_class.run('ls') 27 | end 28 | 29 | it 'works when interactive' do 30 | expect(PTY).to receive(:open).twice 31 | expect(IO).to receive(:pipe).once 32 | expect(described_class).to receive(:spawn) 33 | cmd = described_class.run('ls') 34 | expect(cmd.pid).to eq pid 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/docker_driver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::Docker::Driver do 4 | describe '.docker_for_mac?' do 5 | let(:mac?) { true } 6 | 7 | before do 8 | allow(DockerSync::Environment).to receive(:system).with('pgrep -q com.docker.hyperkit').and_return(true) 9 | allow(DockerSync::Environment).to receive(:mac?).and_return(mac?) 10 | 11 | described_class.remove_instance_variable(:@docker_for_mac) if described_class.instance_variable_defined? :@docker_for_mac 12 | end 13 | 14 | subject { described_class.docker_for_mac? } 15 | 16 | context 'when not running on a Macintosh' do 17 | let(:mac?) { false } 18 | it { is_expected.to be false } 19 | it { is_expected.to execute_nothing } 20 | end 21 | 22 | it 'checks if Docker is running in Hyperkit' do 23 | subject 24 | 25 | expect(DockerSync::Environment).to have_received(:system).with('pgrep -q com.docker.hyperkit') 26 | end 27 | 28 | it 'checks if Docker is running in Virtualization' do 29 | allow(DockerSync::Environment).to receive(:system).with('pgrep -q com.docker.hyperkit').and_return(false) 30 | allow(DockerSync::Environment).to receive(:system).with('pgrep -q com.docker.virtualization').and_return(true) 31 | 32 | is_expected.to be true 33 | expect(DockerSync::Environment).to have_received(:system).with('pgrep -q com.docker.hyperkit') 34 | expect(DockerSync::Environment).to have_received(:system).with('pgrep -q com.docker.virtualization') 35 | end 36 | 37 | it 'is memoized' do 38 | expect { 1.times { described_class.docker_for_mac? } }.to change { described_class.instance_variable_defined?(:@docker_for_mac) } 39 | 40 | expect(DockerSync::Environment).to have_received(:system).exactly(:once) 41 | end 42 | end 43 | 44 | describe '.docker_toolbox?' do 45 | let(:mac?) { true } 46 | let(:docker_machine_available?) { true } 47 | 48 | before do 49 | allow(DockerSync::Environment).to receive(:mac?).and_return(mac?) 50 | allow(DockerSync::Environment).to receive(:system).and_return(true) 51 | allow(described_class).to receive(:find_executable0).with('docker-machine').and_return(docker_machine_available?) 52 | 53 | described_class.remove_instance_variable(:@docker_toolbox) if described_class.instance_variable_defined? :@docker_toolbox 54 | end 55 | 56 | subject { described_class.docker_toolbox? } 57 | 58 | context 'when not running on a Macintosh' do 59 | let(:mac?) { false } 60 | it { is_expected.to be false } 61 | it { is_expected.to execute_nothing } 62 | end 63 | 64 | context 'when docker-machine is not available' do 65 | let(:docker_machine_available?) { false } 66 | it { is_expected.to be false } 67 | it { is_expected.to execute_nothing } 68 | end 69 | 70 | it 'checks if Docker is running in Boot2Docker' do 71 | subject 72 | expect(DockerSync::Environment).to have_received(:system).with('docker info | grep -q "Operating System: Boot2Docker"') 73 | end 74 | 75 | it 'is memoized' do 76 | expect { 2.times { described_class.docker_toolbox? } }.to change { described_class.instance_variable_defined?(:@docker_toolbox) } 77 | expect(DockerSync::Environment).to have_received(:system).exactly(:once) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/docker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'pry' 3 | 4 | RSpec.describe DockerSync::Dependencies::Docker do 5 | it_behaves_like 'a dependency' 6 | it_behaves_like 'a binary-checking dependency', 'docker' 7 | 8 | describe '.running?' do 9 | before do 10 | described_class.remove_instance_variable(:@running) if described_class.instance_variable_defined? :@running 11 | end 12 | 13 | subject { described_class.running? } 14 | 15 | context 'when `docker ps` succeeds' do 16 | before { expect(described_class).to receive(:system).with(/^docker ps/).and_return(true) } 17 | it { is_expected.to be true } 18 | end 19 | 20 | context 'when `docker ps` errors' do 21 | before { expect(described_class).to receive(:system).with(/^docker ps/).and_return(false) } 22 | it { is_expected.to be false } 23 | end 24 | end 25 | 26 | describe 'ensure!' do 27 | let(:available?) { true } 28 | let(:running?) { true } 29 | 30 | before do 31 | allow(described_class).to receive(:available?).and_return(available?) 32 | allow(described_class).to receive(:running?).and_return(running?) 33 | end 34 | 35 | subject { described_class.ensure! } 36 | 37 | context 'when Docker is both available and running' do 38 | it { is_expected_not_to_raise_error } 39 | end 40 | 41 | context 'when Docker is not available' do 42 | let(:available?) { false } 43 | it { is_expected_to_raise_error RuntimeError } 44 | end 45 | 46 | context 'when Docker is not running' do 47 | let(:running?) { false } 48 | it { is_expected_to_raise_error RuntimeError } 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/fswatch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::Fswatch do 4 | before do 5 | allow(DockerSync::Environment).to receive(:mac?).and_return(true) 6 | allow(described_class).to receive(:exit) 7 | end 8 | 9 | it_behaves_like 'a dependency' 10 | it_behaves_like 'a binary-checking dependency', 'fswatch' 11 | it_behaves_like 'a binary-installing dependency', 'fswatch' 12 | end 13 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/package_manager_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::PackageManager do 4 | describe '.package_manager' do 5 | before do 6 | described_class.remove_instance_variable :@package_manager if described_class.instance_variable_defined? :@package_manager 7 | described_class.supported_package_managers.each do |package_manager| 8 | allow(package_manager).to receive(:available?).and_return(false) 9 | end 10 | end 11 | 12 | subject { described_class.package_manager } 13 | 14 | context 'when a package manager is available' do 15 | let(:available_package_manager) { described_class.supported_package_managers.sample } 16 | before { allow(available_package_manager).to receive(:available?).and_return(true) } 17 | it { is_expected.to eq available_package_manager } 18 | end 19 | 20 | context 'when no package manager is available' do 21 | it { is_expected.to eq described_class::None } 22 | end 23 | 24 | it 'is memoized' do 25 | expect { subject }.to change { described_class.instance_variable_defined? :@package_manager }.from(false).to(true) 26 | end 27 | end 28 | 29 | describe '.supported_package_managers' do 30 | subject { described_class.supported_package_managers } 31 | 32 | it 'returns all supported package managers' do 33 | expect(subject).to match_array [ 34 | DockerSync::Dependencies::PackageManager::Brew, 35 | DockerSync::Dependencies::PackageManager::Apt, 36 | DockerSync::Dependencies::PackageManager::Pkg, 37 | DockerSync::Dependencies::PackageManager::Yum 38 | ] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/package_managers/apt_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::PackageManager::Apt do 4 | it_behaves_like 'a dependency' 5 | it_behaves_like 'a binary-checking dependency', 'apt-get' 6 | it_behaves_like 'a package manager' 7 | end 8 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/package_managers/brew_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::PackageManager::Brew do 4 | it_behaves_like 'a dependency' 5 | it_behaves_like 'a binary-checking dependency', 'brew' 6 | it_behaves_like 'a package manager' 7 | end 8 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/package_managers/none_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::PackageManager::None do 4 | it_behaves_like 'a dependency' 5 | 6 | describe '.available?' do 7 | subject { described_class.available? } 8 | it { is_expected.to be true } 9 | end 10 | 11 | describe '.ensure!' do 12 | subject { described_class.ensure! } 13 | it { is_expected.to execute_nothing } 14 | end 15 | 16 | describe '.install_package(package_name)' do 17 | let(:package_name) { 'some-package' } 18 | 19 | before do 20 | allow(described_class).to receive(:ensure!) 21 | allow_any_instance_of(Thor::Shell::Color).to receive_messages( 22 | say_status: nil, 23 | yes?: true 24 | ) 25 | end 26 | 27 | subject { described_class.install_package(package_name) } 28 | 29 | it 'tells user to install any of the supported package managers' do 30 | expect { subject }.to raise_error(described_class::NO_PACKAGE_MANAGER_AVAILABLE) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/package_managers/yum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::PackageManager::Yum do 4 | it_behaves_like 'a dependency' 5 | it_behaves_like 'a binary-checking dependency', 'yum' 6 | it_behaves_like 'a package manager' 7 | end 8 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/rsync_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies::Rsync do 4 | it_behaves_like 'a dependency' 5 | it_behaves_like 'a binary-checking dependency', 'rsync' 6 | it_behaves_like 'a binary-installing dependency', 'rsync' 7 | end 8 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/unison_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # TODO: the unox part should only be called if it is MacOS - its only needed then 4 | RSpec.describe DockerSync::Dependencies::Unison do 5 | before do 6 | allow(DockerSync::Dependencies::PackageManager).to receive(:install_package) 7 | allow(DockerSync::Dependencies::Unox).to receive(:ensure!) 8 | end 9 | 10 | it_behaves_like 'a dependency' 11 | it_behaves_like 'a binary-checking dependency', 'unison' 12 | it_behaves_like 'a binary-installing dependency', 'unison' 13 | 14 | describe '.ensure!' do 15 | before do 16 | allow(described_class).to receive(:available?).and_return(available?) 17 | end 18 | 19 | subject { described_class.ensure! } 20 | 21 | context 'when unison is available' do 22 | let(:available?) { true } 23 | 24 | end 25 | 26 | context 'when unison is not available' do 27 | let(:available?) { false } 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies/unox_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # unox is only available/allowed for mac 4 | RSpec.describe DockerSync::Dependencies::Unox do 5 | let(:available) { true } 6 | let(:fsmonitor_available) { false } 7 | let(:mac?) { true } 8 | 9 | before do 10 | allow(DockerSync::Environment).to receive(:system) 11 | allow(DockerSync::Environment).to receive(:system).with(/^brew list unox/).and_return(available) 12 | allow(DockerSync::Environment).to receive(:system).with(/^brew list unison-fsmonitor/) 13 | .and_return(fsmonitor_available) 14 | allow(DockerSync::Environment).to receive(:mac?).and_return(mac?) 15 | 16 | described_class.remove_instance_variable(:@available) if described_class.instance_variable_defined? :@available 17 | end 18 | 19 | it_behaves_like 'a dependency' 20 | 21 | describe '.available?' do 22 | subject { described_class.available? } 23 | 24 | context "when Unox was installed using brew" do 25 | let(:available) { true } 26 | 27 | it { is_expected.to be true } 28 | end 29 | 30 | context "when Unox was not installed using brew" do 31 | let(:available) { false } 32 | 33 | it { is_expected.to be false } 34 | end 35 | 36 | context 'when Unox was not installed but another unison-fsmonitor is available' do 37 | let(:available) { false } 38 | let(:fsmonitor_available) { true } 39 | 40 | it { is_expected.to be true } 41 | end 42 | 43 | context 'when its not a mac' do 44 | let(:mac?) { false } 45 | 46 | it "unox should not be used - so raise" do 47 | expect{subject}.to raise_error() 48 | end 49 | end 50 | end 51 | 52 | describe '.ensure!' do 53 | subject { described_class.ensure! } 54 | 55 | context 'when it is already available' do 56 | it { is_expected_to_not_raise_error } 57 | end 58 | 59 | context 'when it is not available' do 60 | let(:available) { false } 61 | 62 | before do 63 | allow(DockerSync::Dependencies::PackageManager).to receive(:install_package) 64 | end 65 | 66 | it 'eventually cleans up any non-brew version installed' do 67 | expect(described_class).to receive(:cleanup_non_brew_version!) 68 | subject 69 | end 70 | 71 | it 'tries to install it' do 72 | subject 73 | expect(DockerSync::Dependencies::PackageManager).to have_received(:install_package).with('eugenmayer/dockersync/unox') 74 | end 75 | end 76 | 77 | context 'when its not a mac' do 78 | let(:mac?) { false } 79 | 80 | it "unox should not be used - so raise" do 81 | expect{subject}.to raise_error() 82 | end 83 | end 84 | end 85 | 86 | describe 'cleanup_non_brew_version!' do 87 | before do 88 | allow_any_instance_of(Thor::Shell::Color).to receive(:say_status) 89 | end 90 | 91 | subject { described_class.cleanup_non_brew_version! } 92 | 93 | context 'when a (legacy) non-brew version is not installed' do 94 | before { allow(described_class).to receive(:non_brew_version_installed?).and_return(false) } 95 | 96 | it 'does nothing' do 97 | subject 98 | expect(DockerSync::Environment).to_not have_received(:system) 99 | end 100 | end 101 | 102 | context 'when a (legacy) non-brew version is installed' do 103 | before { allow(described_class).to receive(:non_brew_version_installed?).and_return(true) } 104 | 105 | context 'when user confirms cleanup' do 106 | before { allow_any_instance_of(Thor::Shell::Color).to receive(:yes?).and_return(true) } 107 | 108 | it 'deletes the binary' do 109 | subject 110 | expect(DockerSync::Environment).to have_received(:system).with('sudo rm -f /usr/local/bin/unison-fsmonitor') 111 | end 112 | end 113 | 114 | context 'when user cancels cleanup' do 115 | before { allow_any_instance_of(Thor::Shell::Color).to receive(:yes?).and_return(false) } 116 | it { is_expected_to_raise_error RuntimeError } 117 | end 118 | end 119 | end 120 | 121 | describe '.non_brew_version_installed?' do 122 | subject { described_class.non_brew_version_installed? } 123 | 124 | context 'when brew version is available' do 125 | before { expect(described_class).to receive(:available?).and_return(true) } 126 | it { is_expected.to be false } 127 | end 128 | 129 | context 'when brew version is not available' do 130 | before { expect(described_class).to receive(:available?).and_return(false) } 131 | 132 | context 'when `/usr/local/bin/unison-fsmonitor` exists' do 133 | before { allow(File).to receive(:exist?).with('/usr/local/bin/unison-fsmonitor').and_return(true) } 134 | it { is_expected.to be true } 135 | end 136 | 137 | context 'when `/usr/local/bin/unison-fsmonitor` does not exist' do 138 | before { allow(File).to receive(:exist?).with('/usr/local/bin/unison-fsmonitor').and_return(false) } 139 | it { is_expected.to be false } 140 | end 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/dependencies_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe DockerSync::Dependencies do 4 | let(:config) { double(:config, unison_required?: false, rsync_required?: false, fswatch_required?: false) } 5 | let(:linux?) { false } 6 | let(:freebsd?) { false } 7 | let(:mac?) { false } 8 | 9 | before do 10 | allow(DockerSync::Environment).to receive(:linux?).and_return(linux?) 11 | allow(DockerSync::Environment).to receive(:freebsd?).and_return(freebsd?) 12 | allow(DockerSync::Environment).to receive(:mac?).and_return(mac?) 13 | end 14 | 15 | describe '.ensure_all!(config)' do 16 | before do 17 | allow(described_class).to receive(:ensure_all_for_mac!) 18 | allow(described_class).to receive(:ensure_all_for_freebsd!) 19 | allow(described_class).to receive(:ensure_all_for_linux!) 20 | end 21 | 22 | subject { described_class.ensure_all!(config) } 23 | 24 | context 'when running on a Macintosh' do 25 | let(:mac?) { true } 26 | 27 | it 'delegates to `ensure_all_for_mac!` with given config' do 28 | subject 29 | expect(described_class).to have_received(:ensure_all_for_mac!).with(config) 30 | end 31 | end 32 | 33 | context 'when running on Linux' do 34 | let(:linux?) { true } 35 | 36 | it 'delegates to `ensure_all_for_linux!` with given config' do 37 | subject 38 | expect(described_class).to have_received(:ensure_all_for_linux!).with(config) 39 | end 40 | end 41 | 42 | context 'when running on FreeBSD' do 43 | let(:freebsd?) { true } 44 | 45 | it 'delegates to `ensure_all_for_freebsd!` with given config' do 46 | subject 47 | expect(described_class).to have_received(:ensure_all_for_freebsd!).with(config) 48 | end 49 | end 50 | 51 | context 'when running on another OS' do 52 | it 'raises an error' do 53 | expect { subject }.to raise_error(RuntimeError) 54 | end 55 | end 56 | end 57 | 58 | describe '.ensure_all_for_linux!(_config)' do 59 | let(:linux?) { true } 60 | 61 | before do 62 | allow(described_class::Docker).to receive(:ensure!) 63 | end 64 | 65 | subject { described_class.ensure_all_for_linux!(config) } 66 | 67 | it 'ensures that Docker is available' do 68 | subject 69 | expect(described_class::Docker).to have_received(:ensure!) 70 | end 71 | 72 | context "when FSWatch is required by given `config`" do 73 | before do 74 | allow(config).to receive(:fswatch_required?).and_return(true) 75 | end 76 | 77 | it 'is forbidden' do 78 | expect { subject }.to raise_error(DockerSync::Dependencies::Fswatch::UNSUPPORTED) 79 | end 80 | end 81 | end 82 | 83 | describe '.ensure_all_for_freebsd!(_config)' do 84 | let(:freebsd?) { true } 85 | 86 | before do 87 | allow(described_class::Docker).to receive(:ensure!) 88 | end 89 | 90 | subject { described_class.ensure_all_for_freebsd!(config) } 91 | 92 | it 'ensures that Docker is available' do 93 | subject 94 | expect(described_class::Docker).to have_received(:ensure!) 95 | end 96 | 97 | context "when FSWatch is required by given `config`" do 98 | before do 99 | allow(config).to receive(:fswatch_required?).and_return(true) 100 | end 101 | 102 | it 'is forbidden' do 103 | expect { subject }.to raise_error(DockerSync::Dependencies::Fswatch::UNSUPPORTED) 104 | end 105 | end 106 | end 107 | 108 | describe '.ensure_all_for_mac!(config)' do 109 | let(:mac?) { true } 110 | 111 | before do 112 | allow(described_class::PackageManager).to receive(:ensure!) 113 | allow(described_class::Docker).to receive(:ensure!) 114 | end 115 | 116 | subject { described_class.ensure_all_for_mac!(config) } 117 | 118 | it 'ensures that a package manager is available' do 119 | subject 120 | expect(described_class::PackageManager).to have_received(:ensure!) 121 | end 122 | 123 | it 'ensures that Docker is available' do 124 | subject 125 | expect(described_class::Docker).to have_received(:ensure!) 126 | end 127 | 128 | context "when Unison is required by given `config`" do 129 | before do 130 | allow(config).to receive(:unison_required?).and_return(true) 131 | allow(described_class::Unison).to receive(:ensure!) 132 | end 133 | 134 | it 'ensures that Unison is available' do 135 | subject 136 | expect(described_class::Unison).to have_received(:ensure!) 137 | end 138 | end 139 | 140 | context "when Rsync is required by given `config`" do 141 | before do 142 | allow(config).to receive(:rsync_required?).and_return(true) 143 | allow(described_class::Rsync).to receive(:ensure!) 144 | end 145 | 146 | it 'ensures that Rsync is available' do 147 | subject 148 | expect(described_class::Rsync).to have_received(:ensure!) 149 | end 150 | end 151 | 152 | context "when FSWatch is required by given `config`" do 153 | before do 154 | allow(config).to receive(:fswatch_required?).and_return(true) 155 | allow(described_class::Fswatch).to receive(:ensure!) 156 | end 157 | 158 | it 'ensures that FSWatch is available' do 159 | subject 160 | expect(described_class::Fswatch).to have_received(:ensure!) 161 | end 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/docker_compose_session_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'docker-sync/docker_compose_session' 3 | require 'docker-sync/command' 4 | 5 | RSpec.describe DockerSync::DockerComposeSession do 6 | let(:exitstatus) { 0 } 7 | let(:status) { double('exit status', to_s: "pid 12345 exit #{exitstatus}", to_i: exitstatus) } 8 | let(:output) { 'exit output' } 9 | let(:command) do 10 | double('command', 11 | status: status, 12 | captured_output: output, 13 | captured_error: '') 14 | end 15 | 16 | before do 17 | allow(status).to receive(:success?).and_return(exitstatus == 0) 18 | allow(DockerSync::Command).to receive(:run).and_return(command) 19 | allow(command).to receive(:join).and_return(command) 20 | end 21 | 22 | describe '.new' do 23 | it 'allows file override' do 24 | session = DockerSync::DockerComposeSession.new(files: ['foo.yml']) 25 | expect(DockerSync::Command).to receive(:run).with('docker-compose', '--file=foo.yml', 'up', dir: nil) 26 | session.up 27 | end 28 | 29 | it 'allows more files override' do 30 | session = DockerSync::DockerComposeSession.new(files: ['foo.yml', 'bar.yml', 'buz.yml']) 31 | expect(DockerSync::Command).to receive(:run).with('docker-compose', 32 | '--file=foo.yml', 33 | '--file=bar.yml', 34 | '--file=buz.yml', 35 | 'up', 36 | dir: nil) 37 | session.up 38 | end 39 | 40 | it 'allows file and directory override' do 41 | session = DockerSync::DockerComposeSession.new(files: ['foo.yml'], dir: './tmp') 42 | expect(DockerSync::Command).to receive(:run).with('docker-compose', '--file=foo.yml', 'up', dir: './tmp') 43 | session.up 44 | end 45 | end 46 | 47 | describe '#up' do 48 | it 'runs containers without build option' do 49 | session = DockerSync::DockerComposeSession.new 50 | expect(DockerSync::Command).to receive(:run).with('docker-compose', 'up', dir: nil) 51 | session.up 52 | end 53 | 54 | it 'runs containers with build option' do 55 | session = DockerSync::DockerComposeSession.new 56 | expect(DockerSync::Command).to receive(:run).with('docker-compose', 'up', '--build', dir: nil) 57 | session.up(build: true) 58 | end 59 | 60 | it 'returns captured output' do 61 | session = DockerSync::DockerComposeSession.new 62 | result = session.up 63 | expect(result).to eq 'exit output' 64 | end 65 | end 66 | 67 | describe '#down' do 68 | it 'brings down containers' do 69 | session = DockerSync::DockerComposeSession.new 70 | expect(DockerSync::Command).to receive(:run).with('docker-compose', 'down', dir: nil) 71 | session.down 72 | end 73 | 74 | it 'brings down containers with files: and dir: options' do 75 | session = DockerSync::DockerComposeSession.new(files: ['foo.yml'], dir: './tmp') 76 | expect(DockerSync::Command).to receive(:run).with('docker-compose', '--file=foo.yml', 'down', dir: './tmp') 77 | session.down 78 | end 79 | end 80 | 81 | describe '#stop' do 82 | it 'stops containers' do 83 | session = DockerSync::DockerComposeSession.new 84 | expect(DockerSync::Command).to receive(:run).with('docker-compose', 'stop', dir: nil) 85 | session.stop 86 | end 87 | 88 | it 'stops containers with files: and dir: options' do 89 | session = DockerSync::DockerComposeSession.new(files: ['foo.yml'], dir: './tmp') 90 | expect(DockerSync::Command).to receive(:run).with('docker-compose', '--file=foo.yml', 'stop', dir: './tmp') 91 | session.stop 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/lib/docker-sync/global_config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'securerandom' 3 | require 'tmpdir' 4 | require 'docker-sync/config/global_config' 5 | 6 | RSpec.describe DockerSync::GlobalConfig do 7 | let(:faked_global_config_path) { Pathname.new(Dir.tmpdir).join("#{SecureRandom.hex}.yml") } 8 | 9 | before do 10 | DockerSync::ConfigLocator.global_config_path = faked_global_config_path 11 | end 12 | 13 | subject { 14 | Singleton.__init__(DockerSync::GlobalConfig) 15 | described_class.load 16 | } 17 | 18 | describe '#load' do 19 | it 'initialize with default values if global config file is missing' do 20 | delete_global_config 21 | 22 | expect(subject.to_h).to eql(DockerSync::GlobalConfig::DEFAULTS) 23 | end 24 | 25 | it 'load existing configuration if global config file is found' do 26 | config = {'foo' => 'bar'} 27 | stub_global_config(config) 28 | 29 | expect(subject.to_h).to eql(config) 30 | end 31 | end 32 | 33 | describe '#first_run?' do 34 | it 'return true if global config file is missing' do 35 | delete_global_config 36 | 37 | is_expected.to be_first_run 38 | end 39 | 40 | it 'load existing configuration if global config file is found' do 41 | stub_global_config 42 | 43 | is_expected.not_to be_first_run 44 | end 45 | end 46 | 47 | describe '#update existing!' do 48 | it 'allows updating global config' do 49 | config = {'foo' => 'bar'} 50 | stub_global_config(config) 51 | 52 | subject.update! 'baz' => 'bazmaru' 53 | 54 | updated_config = DockerSync::GlobalConfig.load 55 | expect(updated_config.to_h).to eql('foo' => 'bar', 'baz' => 'bazmaru') 56 | end 57 | end 58 | 59 | 60 | describe '#update from default' do 61 | # we cannot put this into the upper update group since otherwise the modified config will be loaded 62 | # due to the singleton we have in DockerSync::GlobalConfig 63 | it 'allows updating from default configuration' do 64 | delete_global_config 65 | config = DockerSync::GlobalConfig.load 66 | config.update! 'new' => 'value' 67 | 68 | updated_config = DockerSync::GlobalConfig.load 69 | expect(updated_config.to_h).to include('new' => 'value') 70 | end 71 | end 72 | 73 | 74 | def delete_global_config 75 | File.delete(DockerSync::ConfigLocator.current_global_config_path) if File.exist?(DockerSync::ConfigLocator.current_global_config_path) 76 | end 77 | 78 | def stub_global_config(config = {}) 79 | File.open(DockerSync::ConfigLocator.current_global_config_path, 'w') {|f| f.write config.to_yaml } 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/shared_examples/dependencies_shared_example.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'a dependency' do 2 | before do 3 | described_class.remove_instance_variable(:@available) if described_class.instance_variable_defined? :@available 4 | end 5 | 6 | it 'implements `.available?`' do 7 | expect(described_class).to respond_to :available? 8 | end 9 | 10 | it 'implements `.ensure!`' do 11 | expect(described_class).to respond_to :ensure! 12 | end 13 | end 14 | 15 | RSpec.shared_examples 'a binary-checking dependency' do |binary| 16 | describe '.available?' do 17 | let(:binary_exists?) { true } 18 | 19 | before do 20 | described_class.remove_instance_variable(:@available) if described_class.instance_variable_defined? :@available 21 | allow(described_class).to receive(:find_executable0).with(binary).and_return(binary_exists?) 22 | end 23 | 24 | subject { described_class.available? } 25 | 26 | context "when `#{binary}` binary is found in $PATH" do 27 | it { is_expected.to be true } 28 | end 29 | 30 | context "when `#{binary}` binary is not found in $PATH" do 31 | let(:binary_exists?) { false } 32 | it { is_expected.to be false } 33 | end 34 | end 35 | end 36 | 37 | RSpec.shared_examples 'a binary-installing dependency' do |binary| 38 | describe '.ensure!' do 39 | let(:available?) { true } 40 | 41 | before do 42 | allow(described_class).to receive(:available?).and_return(available?) 43 | allow(DockerSync::Dependencies::PackageManager).to receive(:install_package) 44 | end 45 | 46 | subject { described_class.ensure! } 47 | 48 | context "when `#{binary}` is available" do 49 | it { is_expected_not_to_raise_error } 50 | end 51 | 52 | context "when `#{binary}` is not available" do 53 | let(:available?) { false } 54 | 55 | it 'tries to install it' do 56 | subject 57 | expect(DockerSync::Dependencies::PackageManager).to have_received(:install_package).with(binary) 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/shared_examples/package_managers_shared_examples.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'a package manager' do 2 | it 'defines the command used to install a package' do 3 | expect(described_class.private_instance_methods(false)).to include :install_cmd 4 | end 5 | 6 | describe '.install_package(package_name)' do 7 | let(:user_confirmed?) { true } 8 | let(:package_name) { 'some-package' } 9 | 10 | before do 11 | allow(DockerSync::Environment).to receive(:system).and_return(true) 12 | allow(described_class).to receive(:ensure!) 13 | allow_any_instance_of(Thor::Shell::Color).to receive_messages( 14 | say_status: nil, 15 | yes?: user_confirmed? 16 | ) 17 | end 18 | 19 | subject { described_class.install_package(package_name) } 20 | 21 | it 'ensures this package manager is available' do 22 | subject 23 | expect(described_class).to have_received(:ensure!) 24 | end 25 | 26 | it 'asks for user confirmation' do 27 | expect_any_instance_of(Thor::Shell::Color).to receive(:yes?) 28 | subject 29 | end 30 | 31 | context 'when user confirmed installation' do 32 | let(:user_confirmed?) { true } 33 | let(:install_command) { described_class.new(package_name).send(:install_cmd) } 34 | 35 | it 'executes the package installation command' do 36 | subject 37 | expect(DockerSync::Environment).to have_received(:system).with(install_command) 38 | end 39 | end 40 | 41 | context 'when user canceled installation' do 42 | let(:user_confirmed?) { false } 43 | it { is_expected_to_raise_error RuntimeError } 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/shared_examples/synchronization_shared_examples.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'a synchronized directory' do |container_dir, options = {}| 2 | options[:within] ||= 1.second 3 | 4 | it 'synchronizes on startup' do 5 | expect(host_app_path).to be_in_sync_with(container_dir).in_container('docker_sync_specs-sync') 6 | end 7 | 8 | it 'synchronizes files additions' do 9 | File.write(File.join(host_app_path, 'new_file.txt'), 'Hi, I am a new file') 10 | expect(host_app_path).to be_in_sync_with(container_dir).in_container('docker_sync_specs-sync').within(options[:within]) 11 | end 12 | 13 | it 'synchronizes files changes' do 14 | File.write(File.join(host_app_path, 'README.md'), 'Some new content', mode: 'a+') 15 | expect(host_app_path).to be_in_sync_with(container_dir).in_container('docker_sync_specs-sync').within(options[:within]) 16 | end 17 | 18 | it 'synchronizes files deletions' do 19 | File.delete(File.join(host_app_path, 'README.md')) 20 | expect(host_app_path).to be_in_sync_with(container_dir).in_container('docker_sync_specs-sync').within(options[:within]) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'docker-sync' 2 | require 'rspec/bash' 3 | require 'pry' 4 | 5 | # ActiveSupport stuff for readability (specially `1.second` and stuff like that) 6 | require 'active_support' 7 | require 'active_support/core_ext/numeric/time' 8 | require 'active_support/core_ext/integer/time' 9 | require 'active_support/core_ext/object/blank' 10 | 11 | # This file was generated by the `rspec --init` command. Conventionally, all 12 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 13 | # The generated `.rspec` file contains `--require spec_helper` which will cause 14 | # this file to always be loaded, without a need to explicitly require it in any 15 | # files. 16 | # 17 | # Given that it is always loaded, you are encouraged to keep this file as 18 | # light-weight as possible. Requiring heavyweight dependencies from this file 19 | # will add to the boot time of your test suite on EVERY test run, even for an 20 | # individual file that may not need all of that loaded. Instead, consider making 21 | # a separate helper file that requires the additional dependencies and performs 22 | # the additional setup, and require it from the spec files that actually need 23 | # it. 24 | # 25 | # The `.rspec` file also contains a few flags that are not defaults but that 26 | # users commonly want. 27 | # 28 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 29 | RSpec.configure do |config| 30 | # rspec-expectations config goes here. You can use an alternate 31 | # assertion/expectation library such as wrong or the stdlib/minitest 32 | # assertions if you prefer. 33 | config.expect_with :rspec do |expectations| 34 | # This option will default to `true` in RSpec 4. It makes the `description` 35 | # and `failure_message` of custom matchers include text for helper methods 36 | # defined using `chain`, e.g.: 37 | # be_bigger_than(2).and_smaller_than(4).description 38 | # # => "be bigger than 2 and smaller than 4" 39 | # ...rather than: 40 | # # => "be bigger than 2" 41 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 42 | end 43 | 44 | # rspec-mocks config goes here. You can use an alternate test double 45 | # library (such as bogus or mocha) by changing the `mock_with` option here. 46 | config.mock_with :rspec do |mocks| 47 | # Prevents you from mocking or stubbing a method that does not exist on 48 | # a real object. This is generally recommended, and will default to 49 | # `true` in RSpec 4. 50 | mocks.verify_partial_doubles = true 51 | end 52 | 53 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 54 | # have no way to turn it off -- the option exists only for backwards 55 | # compatibility in RSpec 3). It causes shared context metadata to be 56 | # inherited by the metadata hash of host groups and examples, rather than 57 | # triggering implicit auto-inclusion in groups with matching metadata. 58 | config.shared_context_metadata_behavior = :apply_to_host_groups 59 | 60 | # Include rspec-bash globally 61 | config.include Rspec::Bash 62 | 63 | # The settings below are suggested to provide a good initial experience 64 | # with RSpec, but feel free to customize to your heart's content. 65 | 66 | # This allows you to limit a spec run to individual examples or groups 67 | # you care about by tagging them with `:focus` metadata. When nothing 68 | # is tagged with `:focus`, all examples get run. RSpec also provides 69 | # aliases for `it`, `describe`, and `context` that include `:focus` 70 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 71 | config.filter_run_when_matching :focus 72 | 73 | # Allows RSpec to persist some state between runs in order to support 74 | # the `--only-failures` and `--next-failure` CLI options. We recommend 75 | # you configure your source control system to ignore this file. 76 | config.example_status_persistence_file_path = 'spec/examples.txt' 77 | 78 | # Limits the available syntax to the non-monkey patched syntax that is 79 | # recommended. For more details, see: 80 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 81 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 82 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 83 | config.disable_monkey_patching! 84 | 85 | # This setting enables warnings. It's recommended, but in some cases may 86 | # be too noisy due to issues in dependencies. 87 | config.warnings = true 88 | 89 | # Many RSpec users commonly either run the entire suite or an individual 90 | # file, and it's useful to allow more verbose output when running an 91 | # individual spec file. 92 | if config.files_to_run.one? 93 | # Use the documentation formatter for detailed output, 94 | # unless a formatter has already been configured 95 | # (e.g. via a command-line flag). 96 | config.default_formatter = 'doc' 97 | end 98 | 99 | # Print the 10 slowest examples and example groups at the 100 | # end of the spec run, to help surface which specs are running 101 | # particularly slow. 102 | # config.profile_examples = 10 103 | 104 | # Run specs in random order to surface order dependencies. If you find an 105 | # order dependency and want to debug it, you can fix the order by providing 106 | # the seed, which is printed after each run. 107 | # --seed 1234 108 | config.order = :random 109 | 110 | # Seed global randomization in this process using the `--seed` CLI option. 111 | # Setting this allows you to use `--seed` to deterministically reproduce 112 | # test failures related to randomization by passing the same `--seed` value 113 | # as the one that triggered the failure. 114 | Kernel.srand config.seed 115 | end 116 | 117 | Dir[ 118 | File.join(__dir__, 'helpers', '**', '*.rb'), 119 | File.join(__dir__, 'shared_examples', '**', '*.rb') 120 | ].each { |f| require f } 121 | -------------------------------------------------------------------------------- /tasks/daemon/daemon.thor: -------------------------------------------------------------------------------- 1 | require 'docker-sync' 2 | require 'docker-sync/sync_manager' 3 | require 'docker-sync/update_check' 4 | require 'docker-sync/upgrade_check' 5 | require 'daemons' 6 | require 'fileutils' 7 | 8 | class Daemon < Thor 9 | class_option :dir, :aliases => '--dir', :default => './.docker-sync', :type => :string, :desc => 'Path to PID and OUTPUT file Directory' 10 | class_option :logd, :aliases => '--logd', :default => true, :type => :boolean, :desc => 'To log OUPUT to file on Daemon or not' 11 | class_option :app_name, :aliases => '--name', :default => 'daemon', :type => :string, :desc => 'App name used in PID and OUTPUT file name for Daemon' 12 | class_option :logd, :aliases => '--logd', :default => true, :type => :boolean, :desc => 'To log OUPUT to file on Daemon or not' 13 | class_option :config, :aliases => '-c',:default => nil, :type => :string, :desc => 'Path of the docker_sync config' 14 | class_option :sync_name, :aliases => '-n',:type => :string, :desc => 'If given, only this sync configuration will be references/started/synced' 15 | class_option :version, :aliases => '-v',:type => :boolean, :default => false, :desc => 'prints out the version of docker-sync and exits' 16 | 17 | desc 'start', 'Start docker-sync daemon' 18 | def start 19 | say_status 'warning', 'Daemon mode is now the default, just use docker-sync start .. docker-sync-daemon is deprecated', :blue 20 | 21 | opt = options.dup 22 | opt.merge!(:daemon => true) 23 | sync = Sync.new([], opt) 24 | sync.start 25 | end 26 | 27 | desc 'stop', 'Stop docker-sync daemon' 28 | def stop 29 | say_status 'warning', 'Daemon mode is now the default, just use docker-sync start .. docker-sync-daemon is deprecated', :blue 30 | 31 | opt = options.dup 32 | opt.merge!(:daemon => true) 33 | sync = Sync.new([], opt) 34 | sync.stop 35 | end 36 | 37 | desc 'clean', 'Clean docker-sync daemon' 38 | def clean 39 | say_status 'warning', 'Daemon mode is now the default, just use docker-sync start .. docker-sync-daemon is deprecated', :blue 40 | 41 | opt = options.dup 42 | opt.merge!(:daemon => true) 43 | sync = Sync.new([], opt) 44 | sync.clean 45 | end 46 | 47 | desc 'logs', 'Prints last 100 lines of daemon log. Only for use with docker-sync started in background.' 48 | method_option :lines, :aliases => '--lines', :default => 100, :type => :numeric, :desc => 'Specify number of lines to tail' 49 | method_option :follow, :aliases => '-f', :default => false, :type => :boolean, :desc => 'Specify if the logs should be streamed' 50 | def logs 51 | opt = options.dup 52 | sync = Sync.new([], opt) 53 | sync.logs 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /tasks/stack/stack.thor: -------------------------------------------------------------------------------- 1 | require 'docker-sync' 2 | require 'docker-sync/sync_manager' 3 | require 'docker-sync/update_check' 4 | require 'docker-sync/upgrade_check' 5 | require 'docker-sync/compose' 6 | require 'docker-sync/config/project_config' 7 | 8 | class Stack < Thor 9 | class_option :config, :aliases => '-c', :default => nil, :type => :string, :desc => 'Path of the docker_sync config' 10 | class_option :sync_name, :aliases => '-n', :type => :string, :desc => 'If given, only this sync configuration will be references/started/synced' 11 | class_option :version, :aliases => '-v',:type => :boolean, :default => false, :desc => 'prints out the version of docker-sync and exits' 12 | 13 | desc '--version, -v', 'Prints out the version of docker-sync and exits' 14 | def print_version 15 | puts UpgradeChecker.get_current_version 16 | exit(0) 17 | end 18 | map %w[--version -v] => :print_version 19 | 20 | desc 'start', 'Start sync services, watcher and then your docker-compose defined stack' 21 | def start 22 | if options[:version] 23 | puts UpgradeChecker.get_current_version 24 | exit(0) 25 | end 26 | 27 | # do run update check in the start command only 28 | updates = UpdateChecker.new 29 | updates.run 30 | 31 | upgrades = UpgradeChecker.new 32 | upgrades.run 33 | 34 | begin 35 | config = DockerSync::ProjectConfig.new(config_path: options[:config]) 36 | DockerSync::Dependencies.ensure_all!(config) 37 | rescue StandardError => e 38 | say_status 'error', e.message, :red 39 | exit(1) 40 | end 41 | 42 | say_status 'note:', 'You can also run docker-sync in the background with docker-sync start' 43 | 44 | @sync_manager = DockerSync::SyncManager.new(config: config) 45 | @sync_manager.run(options[:sync_name]) 46 | global_options = @sync_manager.global_options 47 | @compose_manager = ComposeManager.new(global_options) 48 | 49 | compose_thread = Thread.new { 50 | @compose_manager.run 51 | } 52 | 53 | begin 54 | compose_thread.join 55 | #@sync_manager.join_threads 56 | rescue SystemExit, Interrupt 57 | say_status 'shutdown', 'Shutting down...', :blue 58 | 59 | @sync_manager.stop 60 | @compose_manager.stop 61 | rescue StandardError => e 62 | puts "EXCEPTION: #{e.inspect}" 63 | puts "MESSAGE: #{e.message}" 64 | end 65 | end 66 | 67 | desc 'clean', 'compose down your app stack, stop and clean up all sync endpoints' 68 | 69 | def clean 70 | if options[:version] 71 | puts UpgradeChecker.get_current_version 72 | exit(0) 73 | end 74 | 75 | begin 76 | config = DockerSync::ProjectConfig.new(config_path: options[:config]) 77 | DockerSync::Dependencies.ensure_all!(config) 78 | rescue StandardError => e 79 | say_status 'error', e.message, :red 80 | exit(1) 81 | end 82 | 83 | @sync_manager = DockerSync::SyncManager.new(config: config) 84 | global_options = @sync_manager.global_options 85 | # shutdown compose first 86 | @compose_manager = ComposeManager.new(global_options) 87 | @compose_manager.clean 88 | say_status 'success', 'Finished cleaning up your app stack', :green 89 | 90 | # now shutdown sync 91 | @sync_manager.clean(options[:sync_name]) 92 | say_status 'success', 'Finished cleanup. Removed stopped, removed sync container and removed their volumes', :green 93 | end 94 | end 95 | --------------------------------------------------------------------------------