docker-provider 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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