percheron 0.5.0 → 0.6.0

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 (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