docker-provider 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +3 -0
  6. data/Gemfile +21 -0
  7. data/Gemfile.lock +125 -0
  8. data/Guardfile +6 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +134 -0
  11. data/Rakefile +20 -0
  12. data/boxes/dummy/README.md +17 -0
  13. data/boxes/dummy/metadata.json +3 -0
  14. data/boxes/nginx/.gitignore +1 -0
  15. data/boxes/nginx/Dockerfile +4 -0
  16. data/boxes/nginx/README.md +25 -0
  17. data/boxes/nginx/Vagrantfile.sample +6 -0
  18. data/boxes/nginx/metadata.json +3 -0
  19. data/boxes/nginx/start +5 -0
  20. data/boxes/precise/.gitignore +1 -0
  21. data/boxes/precise/Dockerfile +42 -0
  22. data/boxes/precise/README.md +18 -0
  23. data/boxes/precise/Vagrantfile.sample +6 -0
  24. data/boxes/precise/metadata.json +3 -0
  25. data/development/Vagrantfile +91 -0
  26. data/docker-provider.gemspec +25 -0
  27. data/example/Vagrantfile +36 -0
  28. data/lib/docker-provider.rb +1 -0
  29. data/lib/docker-provider/action.rb +161 -0
  30. data/lib/docker-provider/action/check_running.rb +25 -0
  31. data/lib/docker-provider/action/create.rb +56 -0
  32. data/lib/docker-provider/action/created.rb +18 -0
  33. data/lib/docker-provider/action/destroy.rb +24 -0
  34. data/lib/docker-provider/action/forward_ports.rb +54 -0
  35. data/lib/docker-provider/action/is_running.rb +20 -0
  36. data/lib/docker-provider/action/message.rb +23 -0
  37. data/lib/docker-provider/action/share_folders.rb +63 -0
  38. data/lib/docker-provider/action/start.rb +18 -0
  39. data/lib/docker-provider/action/stop.rb +21 -0
  40. data/lib/docker-provider/config.rb +28 -0
  41. data/lib/docker-provider/driver.rb +114 -0
  42. data/lib/docker-provider/plugin.rb +24 -0
  43. data/lib/docker-provider/provider.rb +59 -0
  44. data/lib/docker-provider/version.rb +5 -0
  45. data/locales/en.yml +21 -0
  46. data/spec/acceptance/Vagrantfile +25 -0
  47. data/spec/acceptance/vagrant_ssh.bats +34 -0
  48. data/spec/acceptance/vagrant_up.bats +35 -0
  49. data/spec/spec_helper.rb +19 -0
  50. data/spec/support/unit_example_group.rb +39 -0
  51. data/spec/unit/driver_spec.rb +143 -0
  52. metadata +142 -0
@@ -0,0 +1,18 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ class Created
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ machine = env[:machine]
11
+ driver = machine.provider.driver
12
+ env[:result] = machine.id && driver.created?(machine.id)
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ class Destroy
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info I18n.t("vagrant.actions.vm.destroy.destroying")
11
+
12
+ machine = env[:machine]
13
+ config = machine.provider_config
14
+ driver = machine.provider.driver
15
+
16
+ driver.rm(machine.id)
17
+ machine.id = nil
18
+
19
+ @app.call env
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ class ForwardPorts
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ @env = env
11
+
12
+ env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config)
13
+
14
+ if env[:forwarded_ports].any?
15
+ env[:ui].info I18n.t("vagrant.actions.vm.forward_ports.forwarding")
16
+ inform_forwarded_ports(env[:forwarded_ports])
17
+ end
18
+
19
+ # FIXME: Check whether the container has already been created with
20
+ # different exposed ports and let the user know about it
21
+
22
+ @app.call env
23
+ end
24
+
25
+ def inform_forwarded_ports(ports)
26
+ ports.each do |fp|
27
+ message_attributes = {
28
+ :adapter => 'eth0',
29
+ :guest_port => fp[:guest],
30
+ :host_port => fp[:host]
31
+ }
32
+
33
+ @env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.forwarding_entry",
34
+ message_attributes))
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def compile_forwarded_ports(config)
41
+ mappings = {}
42
+
43
+ config.vm.networks.each do |type, options|
44
+ if type == :forwarded_port && options[:id] != 'ssh'
45
+ mappings[options[:host]] = options
46
+ end
47
+ end
48
+
49
+ mappings.values
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,20 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ class IsRunning
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ machine = env[:machine]
11
+ driver = machine.provider.driver
12
+
13
+ env[:result] = driver.running?(machine.id)
14
+
15
+ @app.call(env)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ # XXX: Is this really needed? Should we contribute this back to Vagrant's core?
5
+ class Message
6
+ def initialize(app, env, msg_key, type = :info)
7
+ @app = app
8
+ @msg_key = msg_key
9
+ @type = type
10
+ end
11
+
12
+ def call(env)
13
+ machine = env[:machine]
14
+ message = I18n.t("docker_provider.messages.#{@msg_key}", name: machine.name)
15
+
16
+ env[:ui].send @type, message
17
+
18
+ @app.call env
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ class ShareFolders
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ @env = env
11
+ prepare_folders
12
+
13
+ # FIXME: Check whether the container has already been created with
14
+ # different synced folders and let the user know about it
15
+ folders = []
16
+ shared_folders.each do |id, data|
17
+ folders << {
18
+ :name => id,
19
+ :hostpath => File.expand_path(data[:hostpath], @env[:root_path]),
20
+ :guestpath => data[:guestpath]
21
+ }
22
+ end
23
+ @env[:synced_folders] = folders
24
+
25
+ @app.call(env)
26
+ end
27
+
28
+ # This method returns an actual list of synced folders to create and their
29
+ # proper path.
30
+ def shared_folders
31
+ {}.tap do |result|
32
+ @env[:machine].config.vm.synced_folders.each do |id, data|
33
+ # Ignore disabled shared folders
34
+ next if data[:disabled]
35
+ # This to prevent overwriting the actual shared folders data
36
+ result[id] = data.dup
37
+ end
38
+ end
39
+ end
40
+
41
+ # Prepares the shared folders by verifying they exist and creating them
42
+ # if they don't.
43
+ def prepare_folders
44
+ shared_folders.each do |id, options|
45
+ hostpath = Pathname.new(options[:hostpath]).expand_path(@env[:root_path])
46
+
47
+ if !hostpath.directory? && options[:create]
48
+ # Host path doesn't exist, so let's create it.
49
+ @logger.debug("Host path doesn't exist, creating: #{hostpath}")
50
+
51
+ begin
52
+ hostpath.mkpath
53
+ rescue Errno::EACCES
54
+ raise Vagrant::Errors::SharedFolderCreateFailed,
55
+ :path => hostpath.to_s
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,18 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ class Start
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ machine = env[:machine]
11
+ driver = machine.provider.driver
12
+ driver.start(machine.id)
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ module Action
4
+ class Stop
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ machine = env[:machine]
11
+ driver = machine.provider.driver
12
+ if driver.running?(machine.id)
13
+ env[:ui].info I18n.t("docker_provider.messages.stopping")
14
+ driver.stop(machine.id)
15
+ end
16
+ @app.call(env)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ module VagrantPlugins
2
+ module DockerProvider
3
+ class Config < Vagrant.plugin("2", :config)
4
+ attr_accessor :image, :cmd, :ports
5
+
6
+ def initialize
7
+ @image = UNSET_VALUE
8
+ @cmd = UNSET_VALUE
9
+ @ports = UNSET_VALUE
10
+ end
11
+
12
+ def finalize!
13
+ @ports = [] if @ports == UNSET_VALUE
14
+ @cmd = [] if @cmd == UNSET_VALUE
15
+ end
16
+
17
+ def validate(machine)
18
+ errors = _detected_errors
19
+
20
+ errors << I18n.t("docker_provider.errors.config.image_not_set") if @image == UNSET_VALUE
21
+ # TODO: Detect if base image has a CMD / ENTRYPOINT set before erroring out
22
+ errors << I18n.t("docker_provider.errors.config.cmd_not_set") if @cmd == UNSET_VALUE
23
+
24
+ { "docker-provider" => errors }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,114 @@
1
+ require "vagrant/util/busy"
2
+ require "vagrant/util/subprocess"
3
+ require "vagrant/util/retryable"
4
+
5
+ require 'log4r'
6
+ require 'json'
7
+
8
+ module VagrantPlugins
9
+ module DockerProvider
10
+ class Driver
11
+ include Vagrant::Util::Retryable
12
+
13
+ def initialize
14
+ @logger = Log4r::Logger.new("vagrant::docker::driver")
15
+ end
16
+
17
+ def create(params)
18
+ image = params.fetch(:image)
19
+ ports = Array(params[:ports])
20
+ volumes = Array(params[:volumes])
21
+ name = params.fetch(:name)
22
+ cmd = Array(params.fetch(:cmd))
23
+
24
+ run_cmd = %W(docker run -name #{name} -d)
25
+ run_cmd += ports.map { |p| ['-p', p.to_s] }
26
+ run_cmd += volumes.map { |v| ['-v', v.to_s] }
27
+ run_cmd += %W(-h #{params[:hostname]}) if params[:hostname]
28
+ run_cmd += [image, cmd]
29
+
30
+ retryable(tries: 10, sleep: 1) do
31
+ execute(*run_cmd.flatten).chomp
32
+ end
33
+ end
34
+
35
+ def state(cid)
36
+ case
37
+ when running?(cid)
38
+ :running
39
+ when created?(cid)
40
+ :stopped
41
+ else
42
+ :not_created
43
+ end
44
+ end
45
+
46
+ def created?(cid)
47
+ result = execute('docker', 'ps', '-a', '-q').to_s
48
+ result =~ /^#{Regexp.escape cid}$/
49
+ end
50
+
51
+ def running?(cid)
52
+ result = execute('docker', 'ps', '-q')
53
+ result =~ /^#{Regexp.escape cid}$/m
54
+ end
55
+
56
+ def start(cid)
57
+ unless running?(cid)
58
+ execute('docker', 'start', cid)
59
+ end
60
+ end
61
+
62
+ def stop(cid)
63
+ if running?(cid)
64
+ execute('docker', 'stop', cid)
65
+ end
66
+ end
67
+
68
+ def rm(cid)
69
+ if created?(cid)
70
+ execute('docker', 'rm', cid)
71
+ end
72
+ end
73
+
74
+ def inspect(cid)
75
+ # DISCUSS: Is there a chance that this will change?
76
+ @data ||= JSON.parse(execute('docker', 'inspect', cid)).first
77
+ end
78
+
79
+ private
80
+
81
+ def execute(*cmd, &block)
82
+ result = raw(*cmd, &block)
83
+
84
+ if result.exit_code != 0
85
+ if @interrupted
86
+ @logger.info("Exit code != 0, but interrupted. Ignoring.")
87
+ else
88
+ msg = result.stdout.gsub("\r\n", "\n")
89
+ msg << result.stderr.gsub("\r\n", "\n")
90
+ raise "#{cmd.inspect}\n#{msg}" #Errors::ExecuteError, :command => command.inspect
91
+ end
92
+ end
93
+
94
+ # Return the output, making sure to replace any Windows-style
95
+ # newlines with Unix-style.
96
+ result.stdout.gsub("\r\n", "\n")
97
+ end
98
+
99
+ def raw(*cmd, &block)
100
+ int_callback = lambda do
101
+ @interrupted = true
102
+ @logger.info("Interrupted.")
103
+ end
104
+
105
+ # Append in the options for subprocess
106
+ cmd << { :notify => [:stdout, :stderr] }
107
+
108
+ Vagrant::Util::Busy.busy(int_callback) do
109
+ Vagrant::Util::Subprocess.execute(*cmd, &block)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,24 @@
1
+ require_relative "version"
2
+
3
+ require 'vagrant'
4
+
5
+ I18n.load_path << File.expand_path(File.dirname(__FILE__) + '/../../locales/en.yml')
6
+ I18n.reload!
7
+
8
+ module VagrantPlugins
9
+ module DockerProvider
10
+ class Plugin < Vagrant.plugin("2")
11
+ name "docker-provider"
12
+
13
+ provider(:docker, parallel: true) do
14
+ require_relative 'provider'
15
+ Provider
16
+ end
17
+
18
+ config(:docker, :provider) do
19
+ require_relative 'config'
20
+ Config
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,59 @@
1
+ require_relative 'driver'
2
+ require_relative 'action'
3
+
4
+ module VagrantPlugins
5
+ module DockerProvider
6
+ class Provider < Vagrant.plugin("2", :provider)
7
+ attr_reader :driver
8
+
9
+ def initialize(machine)
10
+ @logger = Log4r::Logger.new("vagrant::provider::docker")
11
+ @machine = machine
12
+ @driver = Driver.new
13
+ end
14
+
15
+ # @see Vagrant::Plugin::V2::Provider#action
16
+ def action(name)
17
+ action_method = "action_#{name}"
18
+ return Action.send(action_method) if Action.respond_to?(action_method)
19
+ nil
20
+ end
21
+
22
+ # Returns the SSH info for accessing the Container.
23
+ def ssh_info
24
+ # If the Container is not created then we cannot possibly SSH into it, so
25
+ # we return nil.
26
+ return nil if state == :not_created
27
+
28
+ network = @driver.inspect(@machine.id)['NetworkSettings']
29
+ ip = network['IPAddress']
30
+
31
+ # If we were not able to identify the container's IP, we return nil
32
+ # here and we let Vagrant core deal with it ;)
33
+ return nil unless ip
34
+
35
+ {
36
+ :host => ip,
37
+ :port => @machine.config.ssh.guest_port
38
+ }
39
+ end
40
+
41
+ def state
42
+ state_id = nil
43
+ state_id = :not_created if !@machine.id || !@driver.created?(@machine.id)
44
+ state_id = @driver.state(@machine.id) if @machine.id && !state_id
45
+ state_id = :unknown if !state_id
46
+
47
+ short = state_id.to_s.gsub("_", " ")
48
+ long = I18n.t("vagrant.commands.status.#{state_id}")
49
+
50
+ Vagrant::MachineState.new(state_id, short, long)
51
+ end
52
+
53
+ def to_s
54
+ id = @machine.id ? @machine.id : "new container"
55
+ "Docker (#{id})"
56
+ end
57
+ end
58
+ end
59
+ end