├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/jpa-buddy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |
20 |
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 |
20 |
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 | [](https://badge.fury.io/rb/docker-sync) [](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 |
--------------------------------------------------------------------------------