percheron 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -3
  3. data/Guardfile +5 -0
  4. data/bin/percheron +8 -4
  5. data/lib/percheron.rb +3 -0
  6. data/lib/percheron/actions.rb +15 -0
  7. data/lib/percheron/actions/base.rb +50 -0
  8. data/lib/percheron/actions/build.rb +47 -0
  9. data/lib/percheron/actions/create.rb +109 -0
  10. data/lib/percheron/actions/exec.rb +51 -0
  11. data/lib/percheron/actions/purge.rb +38 -0
  12. data/lib/percheron/actions/recreate.rb +98 -0
  13. data/lib/percheron/actions/rename.rb +64 -0
  14. data/lib/percheron/actions/restart.rb +31 -0
  15. data/lib/percheron/actions/start.rb +46 -0
  16. data/lib/percheron/actions/stop.rb +27 -0
  17. data/lib/percheron/cli.rb +1 -0
  18. data/lib/percheron/cli/abstract_command.rb +7 -0
  19. data/lib/percheron/cli/create_command.rb +3 -2
  20. data/lib/percheron/cli/list_command.rb +2 -8
  21. data/lib/percheron/cli/main_command.rb +1 -0
  22. data/lib/percheron/cli/purge_command.rb +12 -0
  23. data/lib/percheron/cli/recreate_command.rb +9 -2
  24. data/lib/percheron/cli/restart_command.rb +3 -2
  25. data/lib/percheron/cli/start_command.rb +3 -2
  26. data/lib/percheron/cli/stop_command.rb +3 -2
  27. data/lib/percheron/container.rb +120 -5
  28. data/lib/percheron/errors.rb +0 -1
  29. data/lib/percheron/formatters/stack/table.rb +1 -1
  30. data/lib/percheron/null_container.rb +13 -0
  31. data/lib/percheron/oh_dear.rb +46 -0
  32. data/lib/percheron/stack.rb +90 -31
  33. data/lib/percheron/validators/container.rb +9 -1
  34. data/lib/percheron/version.rb +1 -1
  35. data/percheron.gemspec +2 -0
  36. metadata +45 -11
  37. data/lib/percheron/container/actions.rb +0 -15
  38. data/lib/percheron/container/actions/base.rb +0 -21
  39. data/lib/percheron/container/actions/build.rb +0 -58
  40. data/lib/percheron/container/actions/create.rb +0 -56
  41. data/lib/percheron/container/actions/recreate.rb +0 -110
  42. data/lib/percheron/container/actions/start.rb +0 -58
  43. data/lib/percheron/container/actions/stop.rb +0 -33
  44. data/lib/percheron/container/main.rb +0 -193
  45. data/lib/percheron/container/null.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c71cf3ea4de4f1a1d09949fcb09b55384594c707
4
- data.tar.gz: 20c785069bff584a52244d90496e5f8038f22ee4
3
+ metadata.gz: 57bb2c76f384e9d0ac932c72d1c607bac65f9bd8
4
+ data.tar.gz: 4cfa40df1e9fefad4238f6960e2d4570e59a5502
5
5
  SHA512:
6
- metadata.gz: 5dc2ec2e73b073c075b66980dc74dea1ccc65be4686c98c4f381e850717aa4bdfe4cd61541e0bb05f482efbf72b833c3c08ab85cbd7169326bc592803e5ad379
7
- data.tar.gz: e8ec04f906e78f28bae614abd4fa2a2b16c795ba11e6fe8b23e89d197570dd86180182b6fd8b81b6accf8eb9017ec0efbf27aa9743461906db98f88ac0c3545e
6
+ metadata.gz: 4d16f64f3aa1fb492bd825e92a9d622d812fe61da5d8d957a2dc8002a9d655d556f9df61efab2aa83a93a92bc3725353037b820e2d6f1991d5480785bae93c2c
7
+ data.tar.gz: 03d4ac7067cd65c0599850faa66af3e8a6f22ec7a7ef52f53bff795f8c0517a15a2ec79e4fce94338af9698800c6800c867b4bf2c6aaf657200089ed75781e18
data/Gemfile CHANGED
@@ -3,10 +3,10 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'codeclimate-test-reporter', '~> 0.1', require: nil
6
+ gem 'codeclimate-test-reporter', '~> 0.1', require: false
7
7
  end
8
8
 
9
9
  group :development do
10
- gem 'pry-byebug', '~> 3.0'
11
- gem 'awesome_print', '~> 1.0'
10
+ gem 'pry-byebug', '~> 3.0', require: false
11
+ gem 'awesome_print', '~> 1.0', require: false
12
12
  end
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/bin/percheron CHANGED
@@ -10,10 +10,10 @@ require 'percheron/cli'
10
10
  $metastore = Metastore::Cabinet.new(File.join(ENV['HOME'], '.percheron', 'metastore.yaml'))
11
11
  $logger = Logger.new(STDOUT)
12
12
 
13
- logger_level = Logger::WARN
13
+ logger_level = Logger::INFO
14
14
 
15
- if ENV['VERBOSE'] == 'true'
16
- logger_level = Logger::INFO
15
+ if ENV['QUIET'] == 'true'
16
+ logger_level = Logger::WARN
17
17
  end
18
18
 
19
19
  if ENV['DEBUG'] == 'true' || ENV['DOCKER_DEBUG'] == 'true'
@@ -26,4 +26,8 @@ end
26
26
 
27
27
  $logger.level = logger_level
28
28
 
29
- Percheron::CLI::MainCommand.run
29
+ begin
30
+ Percheron::CLI::MainCommand.run
31
+ rescue => e
32
+ puts Percheron::OhDear.new(e).generate
33
+ end
data/lib/percheron.rb CHANGED
@@ -6,6 +6,7 @@ require 'naught'
6
6
  require 'semantic'
7
7
  require 'metastore'
8
8
 
9
+ require 'percheron/oh_dear'
9
10
  require 'percheron/core_extensions'
10
11
  require 'percheron/version'
11
12
  require 'percheron/config'
@@ -15,6 +16,8 @@ require 'percheron/formatters'
15
16
  require 'percheron/validators'
16
17
  require 'percheron/stack'
17
18
  require 'percheron/container'
19
+ require 'percheron/null_container'
20
+ require 'percheron/actions'
18
21
  require 'percheron/docker_connection'
19
22
 
20
23
  module Percheron
@@ -0,0 +1,15 @@
1
+ require 'percheron/actions/base'
2
+ require 'percheron/actions/stop'
3
+ require 'percheron/actions/start'
4
+ require 'percheron/actions/restart'
5
+ require 'percheron/actions/create'
6
+ require 'percheron/actions/recreate'
7
+ require 'percheron/actions/rename'
8
+ require 'percheron/actions/build'
9
+ require 'percheron/actions/purge'
10
+ require 'percheron/actions/exec'
11
+
12
+ module Percheron
13
+ module Actions
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ module Percheron
2
+ module Actions
3
+ module Base
4
+
5
+ def base_dir
6
+ container.dockerfile.dirname.to_s
7
+ end
8
+
9
+ def in_working_directory(new_dir)
10
+ old_dir = Dir.pwd
11
+ Dir.chdir(new_dir)
12
+ yield
13
+ ensure
14
+ Dir.chdir(old_dir)
15
+ end
16
+
17
+ def now_timestamp
18
+ Time.now.strftime('%Y%m%d%H%M%S')
19
+ end
20
+
21
+ def insert_files!(files)
22
+ files.each do |file|
23
+ file = Pathname.new(File.expand_path(file, base_dir))
24
+ container.image.insert_local('localPath' => file.to_s, 'outputPath' => "/tmp/#{file.basename}").tap do |new_image|
25
+ new_image.tag(repo: container.name, tag: container.version.to_s, force: true)
26
+ end
27
+ end
28
+ end
29
+
30
+ def stop_containers!(containers)
31
+ exec_on_containers!(containers) do |container|
32
+ Stop.new(container).execute! if container.running?
33
+ end
34
+ end
35
+
36
+ def start_containers!(containers)
37
+ exec_on_containers!(containers) do |container|
38
+ Start.new(container, container.dependant_containers.values).execute! unless container.running?
39
+ end
40
+ end
41
+
42
+ def exec_on_containers!(containers)
43
+ containers.inject([]) do |all, container|
44
+ all << container if yield(container)
45
+ all
46
+ end.compact
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ module Percheron
2
+ module Actions
3
+ class Build
4
+
5
+ include Base
6
+
7
+ def initialize(container, nocache: false)
8
+ @container = container
9
+ @nocache = nocache
10
+ end
11
+
12
+ def execute!
13
+ build!
14
+ container
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :container, :nocache
20
+
21
+ def build_opts
22
+ {
23
+ 'dockerfile' => container.dockerfile.basename.to_s,
24
+ 't' => container.image_name,
25
+ 'forcerm' => true,
26
+ 'nocache' => nocache
27
+ }
28
+ end
29
+
30
+ def build!
31
+ in_working_directory(base_dir) do
32
+ execute_pre_build_scripts!
33
+
34
+ $logger.info "Building '#{container.image_name}'"
35
+ Docker::Image.build_from_dir(base_dir, build_opts) do |out|
36
+ $logger.debug '%s' % [ out.strip ]
37
+ end
38
+ end
39
+ end
40
+
41
+ def execute_pre_build_scripts!
42
+ Exec.new(container, container.dependant_containers.values, container.pre_build_scripts, 'PRE build').execute!
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,109 @@
1
+ module Percheron
2
+ module Actions
3
+ class Create
4
+
5
+ include Base
6
+
7
+ def initialize(container, recreate: false)
8
+ @container = container
9
+ @recreate = recreate
10
+ end
11
+
12
+ def execute!(opts={})
13
+ if recreate? || !container.exists?
14
+ create!(opts)
15
+ container
16
+ else
17
+ $logger.debug "Container '#{container.name}' already exists"
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :container, :recreate
24
+
25
+ def base_options
26
+ {
27
+ 'name' => container.name,
28
+ 'Image' => container.image_name,
29
+ 'Hostname' => container.name,
30
+ 'Env' => container.env,
31
+ 'ExposedPorts' => container.exposed_ports,
32
+ 'HostConfig' => {
33
+ 'PortBindings' => port_bindings,
34
+ 'Links' => container.links,
35
+ 'Binds' => container.volumes
36
+ }
37
+ }
38
+ end
39
+
40
+ def host_config_options
41
+ {
42
+ 'HostConfig' => {
43
+ 'PortBindings' => port_bindings,
44
+ 'Links' => container.links,
45
+ 'Binds' => container.volumes
46
+ }
47
+ }
48
+ end
49
+
50
+ def port_bindings
51
+ container.ports.inject({}) do |all, p|
52
+ destination, source = p.split(':')
53
+ all[source] = [ { 'HostPort' => destination } ]
54
+ all
55
+ end
56
+ end
57
+
58
+ def recreate?
59
+ recreate
60
+ end
61
+
62
+ def create!(opts)
63
+ $logger.debug "Container '#{container.name}' does not exist, creating"
64
+ build_image!
65
+ insert_scripts!
66
+ create_container!(opts.fetch(:create, {}))
67
+ execute_post_create_scripts!
68
+ set_dockerfile_md5!
69
+ end
70
+
71
+ def build_image!
72
+ unless container.image_exists?
73
+ $logger.info "Creating '#{container.image_name}' image"
74
+ Build.new(container).execute!
75
+ end
76
+ end
77
+
78
+ def set_dockerfile_md5!
79
+ $logger.info "Setting MD5 for '#{container.name}' container to #{container.current_dockerfile_md5}"
80
+ $metastore.set("#{container.metastore_key}.dockerfile_md5", container.current_dockerfile_md5)
81
+ end
82
+
83
+ def create_container!(opts)
84
+ options = base_options.merge(host_config_options).merge(opts)
85
+
86
+ $logger.info "Creating '%s' container" % options['name']
87
+ Docker::Container.create(options)
88
+ end
89
+
90
+ def insert_scripts!
91
+ insert_post_create_scripts!
92
+ insert_post_start_scripts!
93
+ end
94
+
95
+ def insert_post_create_scripts!
96
+ insert_files!(container.post_create_scripts)
97
+ end
98
+
99
+ def insert_post_start_scripts!
100
+ insert_files!(container.post_start_scripts)
101
+ end
102
+
103
+ def execute_post_create_scripts!
104
+ Exec.new(container, container.dependant_containers.values, container.post_create_scripts, 'POST create').execute!
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,51 @@
1
+ module Percheron
2
+ module Actions
3
+ class Exec
4
+
5
+ include Base
6
+
7
+ def initialize(container, dependant_containers, scripts, description)
8
+ @container = container
9
+ @dependant_containers = dependant_containers
10
+ @scripts = scripts
11
+ @description = description
12
+ end
13
+
14
+ def execute!
15
+ $logger.debug "Executing #{description} scripts '#{scripts.inspect}' on '#{container.name}'"
16
+ started_dependant_containers = start_containers!(dependant_containers)
17
+ execute_scripts_on_running_container!
18
+ stop_containers!(started_dependant_containers)
19
+ container
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :container, :dependant_containers, :scripts, :description
25
+
26
+ def execute_scripts_on_running_container!
27
+ container_running = container.running?
28
+ Start.new(container).execute! unless container_running
29
+ execute_scripts!
30
+ Stop.new(container).execute! unless container_running
31
+ end
32
+
33
+ def execute_scripts!
34
+ scripts.each do |script|
35
+ in_working_directory(base_dir) do
36
+ file = Pathname.new(File.expand_path(script, base_dir))
37
+ execute_command!('/bin/bash -x /tmp/%s 2>&1' % file.basename)
38
+ end
39
+ end
40
+ end
41
+
42
+ def execute_command!(command)
43
+ $logger.info "Executing #{description} '#{command}' for '#{container.name}' container"
44
+ container.docker_container.exec(command.split(' ')) do |device, out|
45
+ $logger.debug '%s: %s' % [ device, out.strip ]
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ module Percheron
2
+ module Actions
3
+ class Purge
4
+
5
+ include Base
6
+
7
+ def initialize(container)
8
+ @container = container
9
+ end
10
+
11
+ def execute!
12
+ stop!
13
+ delete_container! if container.exists?
14
+ delete_image! if container.image_exists?
15
+ container
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :container
21
+
22
+ def stop!
23
+ Stop.new(container).execute!
24
+ end
25
+
26
+ def delete_container!
27
+ $logger.info "Deleting '#{container.name}' container"
28
+ container.docker_container.remove
29
+ end
30
+
31
+ def delete_image!
32
+ $logger.info "Deleting '#{container.image_name}' image"
33
+ container.image.remove
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,98 @@
1
+ module Percheron
2
+ module Actions
3
+ class Recreate
4
+
5
+ include Base
6
+
7
+ def initialize(container, force_recreate: false, delete: false)
8
+ @container = container
9
+ @force_recreate = force_recreate
10
+ @delete = delete
11
+ end
12
+
13
+ def execute!
14
+ if recreate?
15
+ recreate!
16
+ container
17
+ else
18
+ unless dockerfile_md5s_match?
19
+ $logger.warn "Container '#{container.name}' MD5's do not match, consider recreating (bump the version!)"
20
+ else
21
+ $logger.debug "Container '#{container.name}' does not need to be recreated"
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :container, :force_recreate, :delete
29
+
30
+ alias_method :force_recreate?, :force_recreate
31
+ alias_method :delete?, :delete
32
+
33
+ def temporary_name
34
+ '%s_wip' % container.name
35
+ end
36
+
37
+ def stored_dockerfile_md5
38
+ container.dockerfile_md5 || container.current_dockerfile_md5
39
+ end
40
+
41
+ def temporary_container_exists?
42
+ !!Docker::Container.get(temporary_name)
43
+ rescue Docker::Error::NotFoundError
44
+ false
45
+ end
46
+
47
+ def recreate?
48
+ force_recreate? || (!dockerfile_md5s_match? && versions_mismatch? && container.auto_recreate?)
49
+ end
50
+
51
+ def versions_mismatch?
52
+ container.version > container.built_version
53
+ end
54
+
55
+ def dockerfile_md5s_match?
56
+ stored_dockerfile_md5 == container.current_dockerfile_md5
57
+ end
58
+
59
+ def recreate!
60
+ $logger.debug "Container '#{container.name}' exists and will be recreated"
61
+
62
+ unless temporary_container_exists?
63
+ delete_container_and_image! if delete?
64
+ create_container!
65
+ rename_container!
66
+ else
67
+ $logger.debug "Not recreating '#{container.name}' container because temporary container '#{temporary_name}' already exists"
68
+ end
69
+ end
70
+
71
+ def delete_container_and_image!
72
+ delete_container!
73
+ delete_image!
74
+ end
75
+
76
+ def delete_container!
77
+ $logger.info "Deleting '#{container.name}' container"
78
+ stop_containers!([ container ])
79
+ container.docker_container.remove
80
+ end
81
+
82
+ def delete_image!
83
+ $logger.info "Deleting '#{container.name}' image"
84
+ container.image.remove
85
+ end
86
+
87
+ def create_container!
88
+ opts = { create: { 'name' => temporary_name } }
89
+ Create.new(container, recreate: true).execute!(opts)
90
+ end
91
+
92
+ def rename_container!
93
+ Rename.new(container, temporary_name, container.name).execute!
94
+ end
95
+
96
+ end
97
+ end
98
+ end