percheron 0.7.16 → 0.8.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -4
  3. data/CHANGELOG.md +8 -0
  4. data/Guardfile +1 -1
  5. data/lib/percheron/actions/build.rb +14 -7
  6. data/lib/percheron/actions/create.rb +50 -39
  7. data/lib/percheron/actions/exec.rb +8 -9
  8. data/lib/percheron/actions/exec_local.rb +1 -2
  9. data/lib/percheron/actions/purge.rb +18 -13
  10. data/lib/percheron/actions/restart.rb +1 -2
  11. data/lib/percheron/actions/shell.rb +4 -4
  12. data/lib/percheron/actions/start.rb +5 -6
  13. data/lib/percheron/actions/stop.rb +0 -1
  14. data/lib/percheron/actions.rb +0 -1
  15. data/lib/percheron/commands/abstract.rb +11 -6
  16. data/lib/percheron/commands/build.rb +3 -2
  17. data/lib/percheron/commands/console.rb +0 -5
  18. data/lib/percheron/commands/create.rb +7 -2
  19. data/lib/percheron/commands/logs.rb +1 -1
  20. data/lib/percheron/commands/main.rb +3 -4
  21. data/lib/percheron/commands/purge.rb +11 -2
  22. data/lib/percheron/commands/restart.rb +1 -1
  23. data/lib/percheron/commands/shell.rb +1 -1
  24. data/lib/percheron/commands/start.rb +1 -1
  25. data/lib/percheron/commands/stop.rb +1 -1
  26. data/lib/percheron/commands.rb +0 -1
  27. data/lib/percheron/config.rb +8 -5
  28. data/lib/percheron/errors.rb +1 -0
  29. data/lib/percheron/graph.rb +3 -4
  30. data/lib/percheron/logger.rb +1 -1
  31. data/lib/percheron/stack.rb +44 -41
  32. data/lib/percheron/unit/image_helper.rb +33 -0
  33. data/lib/percheron/unit.rb +18 -35
  34. data/lib/percheron/version.rb +1 -1
  35. data/lib/percheron.rb +1 -0
  36. data/percheron.gemspec +1 -1
  37. metadata +5 -6
  38. data/lib/percheron/actions/recreate.rb +0 -51
  39. data/lib/percheron/commands/recreate.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4b3358819570477f97ff3f4c1b21b689a7007390
4
- data.tar.gz: 04f82dae87c48a51b0ebff986bf4bab889036c7b
3
+ metadata.gz: 4c4858cd5f4ec9b26b7976179c56f074a1cb5570
4
+ data.tar.gz: 050b74b700b9c127766e5454eba7aea97c85ba7c
5
5
  SHA512:
6
- metadata.gz: 4cafe93d3bddf33b7098cdae3aa87afbe4a4a27d78f65b3e3a36c96808cd9867bbd2881a3a6499f7f334d97a1f8565402129117c93b32cbe32b04a44432005d0
7
- data.tar.gz: 502ca1ee96c79cda8f55b347cb1b3ba30f61645374540a057143ea4b3b6ff0cca0ce06ab450f982e812a315ecd5798b9cd12ca6e1875a5fe4550af62485a8e85
6
+ metadata.gz: 9865aad4f0d56c8e2607bd74dc579443d7c645afc20c1362b855ad867c09475080d5588ffcdf89f86ed89e5cf7ab0e3791b2f85cc8e91565b54fda597198ecb0
7
+ data.tar.gz: 438581d49d52687a2d06cf5d6bd538b3f1bf0df2dfe7b5dab655e1b2bd5a87f62fa5a194f559a3b41dfdf5bebeb32f54aadc24c0a4be3bdc5f6f510ee7bd31ef
data/.rubocop.yml CHANGED
@@ -19,10 +19,6 @@ Metrics/LineLength:
19
19
  Style/GlobalVars:
20
20
  Enabled: false
21
21
 
22
- Style/RegexpLiteral:
23
- EnforcedStyle: mixed
24
- AllowInnerSlashes: false
25
-
26
22
  Metrics/ClassLength:
27
23
  CountComments: false
28
24
  Max: 150
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## v0.8.0 / 2015-08-19
2
+
3
+ * Hostname is the full `<stack_name>_<name>` by default
4
+ * Support for defining restart policy for each unit
5
+ * Support privileged mode
6
+ * Tidied up (re)create, (re)build and log commands
7
+ * Improved logging and error handling
8
+
1
9
  ## v0.7.16 / 2015-08-07
2
10
 
3
11
  * Improved graph look
data/Guardfile CHANGED
@@ -1,5 +1,5 @@
1
1
  guard :rspec, cmd: 'bundle exec rspec' do
2
2
  watch(%r{^spec\/.+_spec\.rb$})
3
3
  watch(%r{^lib\/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
4
- watch('spec/spec_helper.rb') { 'spec' }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
5
  end
@@ -1,12 +1,11 @@
1
1
  module Percheron
2
2
  module Actions
3
3
  class Build
4
-
5
4
  include Base
6
5
 
7
- def initialize(unit, nocache: false, forcerm: false, exec_scripts: true)
6
+ def initialize(unit, usecache: true, forcerm: false, exec_scripts: true)
8
7
  @unit = unit
9
- @nocache = nocache
8
+ @usecache = usecache
10
9
  @forcerm = forcerm
11
10
  @exec_scripts = exec_scripts
12
11
  end
@@ -22,15 +21,17 @@ module Percheron
22
21
 
23
22
  private
24
23
 
25
- attr_reader :unit, :nocache, :forcerm, :exec_scripts
24
+ attr_reader :unit, :usecache, :forcerm, :exec_scripts
25
+ alias_method :usecache?, :usecache
26
+ alias_method :forcerm?, :forcerm
26
27
  alias_method :exec_scripts?, :exec_scripts
27
28
 
28
29
  def options
29
30
  {
30
31
  'dockerfile' => dockerfile,
31
32
  't' => unit.image_name,
32
- 'forcerm' => forcerm,
33
- 'nocache' => nocache
33
+ 'forcerm' => forcerm?,
34
+ 'nocache' => !usecache?
34
35
  }
35
36
  end
36
37
 
@@ -67,13 +68,19 @@ module Percheron
67
68
  execute_pre_build_scripts!
68
69
  $logger.info "Building '#{unit.image_name}' image"
69
70
  Connection.perform(Docker::Image, :build_from_dir, base_dir, options) do |out|
70
- $logger.debug '%s' % [ out.strip ]
71
+ $logger.info '%s' % [ extract_content(out) ]
71
72
  end
72
73
  end
73
74
  ensure
74
75
  remove_temp_dockerfile!
75
76
  end
76
77
 
78
+ def extract_content(out)
79
+ json = JSON.parse(out)
80
+ return '' unless json['stream']
81
+ json['stream'].strip
82
+ end
83
+
77
84
  def execute_pre_build_scripts!
78
85
  return nil if !exec_scripts? && unit.pre_build_scripts.empty?
79
86
  ExecLocal.new(unit, unit.pre_build_scripts, 'PRE build').execute!
@@ -1,36 +1,32 @@
1
1
  module Percheron
2
2
  module Actions
3
3
  class Create
4
-
5
4
  include Base
6
5
 
7
- def initialize(unit, start: false, cmd: false, exec_scripts: true)
6
+ def initialize(unit, build: true, start: false, force: false, cmd: false)
8
7
  @unit = unit
8
+ @build = build
9
9
  @start = start
10
- @exec_scripts = exec_scripts
11
- @cmd = cmd
12
- @unit_image_existed = unit.image_exists?
10
+ @force = force
11
+ @cmd = (cmd || unit.start_args)
13
12
  end
14
13
 
15
14
  def execute!
16
15
  results = []
17
- if unit.exists?
18
- $logger.debug "Unit '#{unit.display_name}' already exists"
19
- else
20
- results << create!
21
- end
16
+ results << build_or_pull_image!
17
+ results << create!
22
18
  results.compact.empty? ? nil : unit
23
19
  end
24
20
 
25
21
  private
26
22
 
27
- attr_reader :unit, :start, :exec_scripts, :unit_image_existed
23
+ attr_reader :unit, :build, :start, :force, :cmd
24
+ alias_method :build?, :build
28
25
  alias_method :start?, :start
29
- alias_method :exec_scripts?, :exec_scripts
30
- alias_method :unit_image_existed?, :unit_image_existed
26
+ alias_method :force?, :force
31
27
 
32
- def cmd
33
- @cmd ||= (@cmd || unit.start_args)
28
+ def create?
29
+ unit.startable? && (!unit.exists? || force)
34
30
  end
35
31
 
36
32
  def base_options
@@ -46,19 +42,25 @@ module Percheron
46
42
  end
47
43
 
48
44
  def host_config_options
49
- config = {
50
- 'HostConfig' => {
45
+ {
46
+ 'HostConfig' => {
51
47
  'PortBindings' => port_bindings,
52
48
  'Links' => unit.links,
53
- 'Binds' => unit.volumes
49
+ 'Binds' => unit.volumes,
50
+ 'RestartPolicy' => unit.restart_policy,
51
+ 'Privileged' => unit.privileged
54
52
  }
55
53
  }
56
- config['Dns'] = unit.dns unless unit.dns.empty?
57
- config
54
+ end
55
+
56
+ def host_config_dns_options
57
+ unit.dns.empty? ? {} : { 'HostConfig' => { 'Dns' => unit.dns } }
58
58
  end
59
59
 
60
60
  def options
61
- @options ||= base_options.merge(host_config_options)
61
+ @options ||= begin
62
+ base_options.merge(host_config_options).merge(host_config_dns_options)
63
+ end
62
64
  end
63
65
 
64
66
  def port_bindings
@@ -68,49 +70,58 @@ module Percheron
68
70
  end
69
71
  end
70
72
 
71
- def create!
73
+ def build_or_pull_image!
72
74
  unit.buildable? ? build_image! : pull_image!
73
- return unless unit.startable?
74
- insert_scripts!
75
- create_unit!
76
- update_dockerfile_md5!
77
- start!
75
+ end
76
+
77
+ def create!
78
+ if create?
79
+ create_unit!
80
+ update_dockerfile_md5!
81
+ start_and_insert_scripts! if start?
82
+ else
83
+ $logger.warn("Unit '#{unit.display_name}' already exists (--force to overwrite)")
84
+ end
85
+ rescue Errors::DockerContainerCannotDelete => e
86
+ $logger.error "Unable to delete '%s' unit - %s" % [ unit.name, e.inspect ]
78
87
  end
79
88
 
80
89
  def build_image!
81
- Build.new(unit).execute! unless unit.image_exists?
90
+ Build.new(unit).execute! if build?
82
91
  end
83
92
 
84
- # FIXME: move this
85
93
  def pull_image!
86
94
  return nil if unit.image_exists?
87
95
  $logger.info "Pulling '#{unit.image_name}' image"
88
96
  Connection.perform(Docker::Image, :create, fromImage: unit.image_name) do |out|
89
- $logger.debug JSON.parse(out)
97
+ $logger.info JSON.parse(out)
90
98
  end
91
99
  end
92
100
 
101
+ def delete_unit!
102
+ $logger.info "Deleting '#{unit.display_name}' unit"
103
+ unit.container.remove(force: force?)
104
+ rescue Docker::Error::ConflictError => e
105
+ raise(Errors::DockerContainerCannotDelete.new, e)
106
+ end
107
+
93
108
  def create_unit!
109
+ delete_unit! if force?
94
110
  $logger.info "Creating '#{unit.display_name}' unit"
95
111
  Connection.perform(Docker::Container, :create, options)
96
112
  end
97
113
 
98
- def start!
99
- return nil if !unit.startable? || !start?
114
+ def start_and_insert_scripts!
100
115
  Start.new(unit).execute!
116
+ insert_post_start_scripts!
101
117
  end
102
118
 
103
119
  def update_dockerfile_md5!
104
120
  unit.update_dockerfile_md5!
105
121
  end
106
122
 
107
- def insert_scripts!
108
- return nil if unit_image_existed?
109
- insert_files!(unit.post_start_scripts)
110
- end
111
-
112
- def insert_files!(files)
113
- files.each { |file| insert_file!(file) }
123
+ def insert_post_start_scripts!
124
+ unit.post_start_scripts.each { |file| insert_file!(file) }
114
125
  end
115
126
 
116
127
  def insert_file!(file)
@@ -1,12 +1,11 @@
1
1
  module Percheron
2
2
  module Actions
3
3
  class Exec
4
-
5
4
  include Base
6
5
 
7
- def initialize(unit, dependant_units, scripts, description)
6
+ def initialize(unit, needed_units, scripts, description)
8
7
  @unit = unit
9
- @dependant_units = dependant_units
8
+ @needed_units = needed_units
10
9
  @scripts = scripts
11
10
  @description = description
12
11
  end
@@ -19,13 +18,13 @@ module Percheron
19
18
 
20
19
  private
21
20
 
22
- attr_reader :unit, :dependant_units, :scripts, :description
21
+ attr_reader :unit, :needed_units, :scripts, :description
23
22
 
24
23
  def exec!
25
24
  results = []
26
- started_dependant_units = start_units!(dependant_units)
25
+ started_needed_units = start_units!(needed_units)
27
26
  results << execute_scripts_on_running_unit!
28
- results << stop_units!(started_dependant_units)
27
+ results << stop_units!(started_needed_units)
29
28
  results
30
29
  end
31
30
 
@@ -34,7 +33,7 @@ module Percheron
34
33
  Start.new(unit, exec_scripts: false).execute! unless unit_running
35
34
  execute_scripts!
36
35
  commit_and_tag_new_image!
37
- Stop.new(unit).execute! unless unit_running
36
+ Stop.new(unit).execute! unless unit_running
38
37
  end
39
38
 
40
39
  def commit_and_tag_new_image!
@@ -67,8 +66,8 @@ module Percheron
67
66
  def start_units!(units, scripts: true)
68
67
  exec_on_units!(units) do |unit|
69
68
  next if unit.running?
70
- units = unit.startable_dependant_units.values
71
- Start.new(unit, dependant_units: units, exec_scripts: scripts).execute!
69
+ units = unit.startable_needed_units.values
70
+ Start.new(unit, needed_units: units, exec_scripts: scripts).execute!
72
71
  end
73
72
  end
74
73
 
@@ -3,7 +3,6 @@ require 'open3'
3
3
  module Percheron
4
4
  module Actions
5
5
  class ExecLocal
6
-
7
6
  include Base
8
7
 
9
8
  def initialize(unit, scripts, description)
@@ -26,7 +25,7 @@ module Percheron
26
25
  $logger.debug "Executing #{description} scripts '#{scripts.inspect}' locally"
27
26
  scripts.each do |script|
28
27
  in_working_directory(base_dir) do
29
- execute_command!('/bin/sh -x %s 2>&1' % Pathname.new(File.expand_path(script)))
28
+ execute_command!('/bin/sh -x %s 2>&1' % [ Pathname.new(File.expand_path(script)) ])
30
29
  end
31
30
  end
32
31
  end
@@ -1,7 +1,6 @@
1
1
  module Percheron
2
2
  module Actions
3
3
  class Purge
4
-
5
4
  include Base
6
5
 
7
6
  def initialize(unit, force: false)
@@ -12,8 +11,8 @@ module Percheron
12
11
  def execute!
13
12
  results = []
14
13
  results << stop!
15
- results << delete_unit!
16
- results << delete_image!
14
+ results << delete_unit! if delete_unit?
15
+ results << delete_image! if delete_image?
17
16
  results.compact.empty? ? nil : unit
18
17
  end
19
18
 
@@ -25,26 +24,32 @@ module Percheron
25
24
  Stop.new(unit).execute!
26
25
  end
27
26
 
27
+ def delete_unit?
28
+ unit.exists?
29
+ end
30
+
28
31
  def delete_image?
29
32
  unit.image_exists? && unit.buildable?
30
33
  end
31
34
 
35
+ def opts
36
+ { force: force }
37
+ end
38
+
32
39
  def delete_unit!
33
- return nil unless unit.exists?
34
- $logger.info "Deleting '#{unit.display_name}' unit"
35
- unit.container.remove(force: force)
36
- rescue Docker::Error::ConflictError => e
37
- $logger.error "Unable to delete '%s' unit - %s" % [ unit.name, e.inspect ]
40
+ delete!('unit', unit.display_name) { unit.container.remove(opts) }
38
41
  end
39
42
 
40
43
  def delete_image!
41
- return nil unless delete_image?
42
- $logger.info "Deleting '#{unit.image_name}' image"
43
- unit.image.remove(force: force)
44
- rescue Docker::Error::ConflictError => e
45
- $logger.error "Unable to delete '%s' image - %s" % [ unit.image_name, e.inspect ]
44
+ delete!('image', unit.image_name) { unit.image.remove(opts) }
46
45
  end
47
46
 
47
+ def delete!(type, value)
48
+ $logger.info("Deleting '%s' %s" % [ value, type ])
49
+ yield
50
+ rescue Docker::Error::ConflictError => e
51
+ $logger.error("Unable to delete '%s' %s - %s" % [ value, type, e.inspect ])
52
+ end
48
53
  end
49
54
  end
50
55
  end
@@ -1,7 +1,6 @@
1
1
  module Percheron
2
2
  module Actions
3
3
  class Restart
4
-
5
4
  include Base
6
5
 
7
6
  def initialize(unit)
@@ -24,7 +23,7 @@ module Percheron
24
23
  end
25
24
 
26
25
  def start!
27
- opts = { dependant_units: unit.startable_dependant_units.values }
26
+ opts = { needed_units: unit.startable_needed_units.values }
28
27
  Start.new(unit, opts).execute!
29
28
  end
30
29
 
@@ -6,9 +6,9 @@ module Percheron
6
6
  DEFAULT_COMMAND = '/bin/sh'
7
7
  DOCKER_CLIENT = 'docker'
8
8
 
9
- def initialize(unit, command: DEFAULT_COMMAND)
9
+ def initialize(unit, raw_command: DEFAULT_COMMAND)
10
10
  @unit = unit
11
- @command = command
11
+ @raw_command = raw_command
12
12
  end
13
13
 
14
14
  def execute!
@@ -17,14 +17,14 @@ module Percheron
17
17
 
18
18
  private
19
19
 
20
- attr_reader :unit
20
+ attr_reader :unit, :raw_command
21
21
 
22
22
  def valid?
23
23
  Validators::DockerClient.new.valid?
24
24
  end
25
25
 
26
26
  def command
27
- "sh -c '%s'" % @command
27
+ "sh -c '%s'" % [ raw_command ]
28
28
  end
29
29
 
30
30
  def exec!
@@ -1,12 +1,11 @@
1
1
  module Percheron
2
2
  module Actions
3
3
  class Start
4
-
5
4
  include Base
6
5
 
7
- def initialize(unit, dependant_units: [], cmd: false, exec_scripts: true)
6
+ def initialize(unit, needed_units: [], cmd: false, exec_scripts: true)
8
7
  @unit = unit
9
- @dependant_units = dependant_units
8
+ @needed_units = needed_units
10
9
  @cmd = cmd
11
10
  @exec_scripts = exec_scripts
12
11
  end
@@ -23,7 +22,7 @@ module Percheron
23
22
 
24
23
  private
25
24
 
26
- attr_reader :unit, :dependant_units, :cmd, :exec_scripts
25
+ attr_reader :unit, :needed_units, :cmd, :exec_scripts
27
26
 
28
27
  def exec_scripts?
29
28
  !unit.post_start_scripts.empty? && exec_scripts
@@ -31,7 +30,7 @@ module Percheron
31
30
 
32
31
  def create!
33
32
  return nil if unit.exists?
34
- Create.new(unit, cmd: cmd, exec_scripts: exec_scripts).execute!
33
+ Create.new(unit, cmd: cmd).execute!
35
34
  end
36
35
 
37
36
  def start!
@@ -42,7 +41,7 @@ module Percheron
42
41
 
43
42
  def execute_post_start_scripts!
44
43
  scripts = unit.post_start_scripts
45
- Exec.new(unit, dependant_units, scripts, 'POST start').execute! if exec_scripts?
44
+ Exec.new(unit, needed_units, scripts, 'POST start').execute! if exec_scripts?
46
45
  end
47
46
 
48
47
  end
@@ -1,7 +1,6 @@
1
1
  module Percheron
2
2
  module Actions
3
3
  class Stop
4
-
5
4
  include Base
6
5
 
7
6
  def initialize(unit)
@@ -3,7 +3,6 @@ require 'percheron/actions/stop'
3
3
  require 'percheron/actions/start'
4
4
  require 'percheron/actions/restart'
5
5
  require 'percheron/actions/create'
6
- require 'percheron/actions/recreate'
7
6
  require 'percheron/actions/build'
8
7
  require 'percheron/actions/purge'
9
8
  require 'percheron/actions/exec'
@@ -17,16 +17,24 @@ module Percheron
17
17
  end
18
18
  end
19
19
 
20
- def self.default_create_parameters!
21
- default_parameters!
22
- option('--start', :flag, 'Start unit', default: false)
20
+ def runit
21
+ yield
22
+ rescue Docker::Error::UnexpectedResponseError => e
23
+ $logger.error('')
24
+ $logger.error('An exception occurred :(')
25
+ $logger.error('')
26
+ $logger.error(e.inspect)
23
27
  end
24
28
 
25
29
  def execute
26
30
  stack.valid?
31
+ rescue Errno::ENOENT, Errors::ConfigFileInvalid, Errors::StackInvalid => e
32
+ signal_usage_error(e.message)
33
+ exit(1)
27
34
  rescue => e
28
35
  puts "%s\n\n%s\n\n" % [ e.inspect, e.backtrace.join("\n") ]
29
36
  signal_usage_error(e.message)
37
+ exit(1)
30
38
  end
31
39
 
32
40
  def stack
@@ -40,9 +48,6 @@ module Percheron
40
48
  Percheron::Connection.load!(c)
41
49
  end
42
50
  end
43
- rescue Errors::ConfigFileInvalid => e
44
- $logger.error e.inspect
45
- exit(1)
46
51
  end
47
52
  end
48
53
  end
@@ -3,11 +3,12 @@ module Percheron
3
3
  class Build < Abstract
4
4
 
5
5
  default_parameters!
6
- option('--forcerm', :flag, 'force removal of intermediate containers', default: false)
6
+ option('--usecache', :flag, 'Use image cache', default: true)
7
+ option('--forcerm', :flag, 'Force removal of intermediate containers', default: false)
7
8
 
8
9
  def execute
9
10
  super
10
- stack.build!(unit_names: unit_names, forcerm: forcerm?)
11
+ runit { stack.build!(unit_names: unit_names, usecache: usecache?, forcerm: forcerm?) }
11
12
  end
12
13
  end
13
14
  end
@@ -32,11 +32,6 @@ module Percheron
32
32
  nil
33
33
  end
34
34
 
35
- def recreate(unit_names, start: false)
36
- stack.create!(unit_names: [ *unit_names ], start: start)
37
- nil
38
- end
39
-
40
35
  def start(unit_names)
41
36
  stack.start!(unit_names: [ *unit_names ])
42
37
  nil
@@ -2,11 +2,16 @@ module Percheron
2
2
  module Commands
3
3
  class Create < Abstract
4
4
 
5
- default_create_parameters!
5
+ default_parameters!
6
+ option('--start', :flag, '(Re)start unit once created', default: true)
7
+ option('--build', :flag, '(Re)build image', default: true)
8
+ option('--deep', :flag, 'Include needed units', default: false)
9
+ option('--force', :flag, 'Force unit (re)creation', default: false)
6
10
 
7
11
  def execute
8
12
  super
9
- stack.create!(unit_names: unit_names, start: start?)
13
+ opts = { unit_names: unit_names, build: build?, start: start?, deep: deep?, force: force? }
14
+ runit { stack.create!(opts) }
10
15
  end
11
16
  end
12
17
  end
@@ -4,7 +4,7 @@ module Percheron
4
4
 
5
5
  parameter('STACK_NAME', 'stack name', required: true)
6
6
  parameter('UNIT_NAME', 'unit name', required: true)
7
- option('--follow', :flag, 'follow the logs', default: false)
7
+ option([ '-f', '-t', '--follow', '--tail' ], :flag, 'Follow the logs', default: false)
8
8
 
9
9
  def execute
10
10
  super
@@ -2,13 +2,12 @@ module Percheron
2
2
  module Commands
3
3
  class Main < Abstract
4
4
  subcommand %w(list status), 'List stacks and its units', List
5
- subcommand 'console', 'Start a pry console session', Console
5
+ subcommand 'console', '', Console
6
6
  subcommand 'start', 'Start a stack', Start
7
7
  subcommand 'stop', 'Stop a stack', Stop
8
8
  subcommand 'restart', 'Restart a stack', Restart
9
- subcommand 'build', 'Build images for a stack', Build
10
- subcommand 'create', 'Build images and create units for a stack', Create
11
- subcommand 'recreate', 'Recreate a stack', Recreate
9
+ subcommand %w(build rebuild), '(Re)build image(s) for a stack', Build
10
+ subcommand %w(create recreate), '(Re)build image(s) and (re)create units for a stack', Create
12
11
  subcommand 'purge', 'Purge a stack', Purge
13
12
  subcommand 'shell', 'Shell into a unit', Shell
14
13
  subcommand 'logs', 'Show logs for a unit', Logs
@@ -3,12 +3,21 @@ module Percheron
3
3
  class Purge < Abstract
4
4
 
5
5
  default_parameters!
6
- option([ '-f', '--force' ], :flag, 'Force unit/image removal', default: false)
6
+ option('--yes', :flag, 'Yes, purge image / unit', default: false)
7
+ option('--force', :flag, 'Force image / unit removal', default: false)
7
8
 
8
9
  def execute
9
10
  super
10
- stack.purge!(unit_names: unit_names, force: force?)
11
+ runit { stack.purge!(unit_names: unit_names, force: force?) if yes? || confirm? }
11
12
  end
13
+
14
+ private
15
+
16
+ def confirm?
17
+ ask('Are you sure? (y|n) ') do |q|
18
+ q.validate = /y(es)?|n(o)?/i
19
+ end.match(/y(es)?/i)
20
+ end
12
21
  end
13
22
  end
14
23
  end
@@ -6,7 +6,7 @@ module Percheron
6
6
 
7
7
  def execute
8
8
  super
9
- stack.restart!(unit_names: unit_names)
9
+ runit { stack.restart!(unit_names: unit_names) }
10
10
  end
11
11
  end
12
12
  end
@@ -8,7 +8,7 @@ module Percheron
8
8
 
9
9
  def execute
10
10
  super
11
- stack.shell!(unit_name, command: command)
11
+ stack.shell!(unit_name, raw_command: command)
12
12
  rescue Errors::DockerClientInvalid => e
13
13
  signal_usage_error(e.message)
14
14
  end
@@ -6,7 +6,7 @@ module Percheron
6
6
 
7
7
  def execute
8
8
  super
9
- stack.start!(unit_names: unit_names)
9
+ runit { stack.start!(unit_names: unit_names) }
10
10
  end
11
11
  end
12
12
  end
@@ -6,7 +6,7 @@ module Percheron
6
6
 
7
7
  def execute
8
8
  super
9
- stack.stop!(unit_names: unit_names)
9
+ runit { stack.stop!(unit_names: unit_names) }
10
10
  end
11
11
  end
12
12
  end
@@ -9,7 +9,6 @@ require 'percheron/commands/purge'
9
9
  require 'percheron/commands/console'
10
10
  require 'percheron/commands/create'
11
11
  require 'percheron/commands/build'
12
- require 'percheron/commands/recreate'
13
12
  require 'percheron/commands/shell'
14
13
  require 'percheron/commands/logs'
15
14
  require 'percheron/commands/graph'
@@ -78,7 +78,8 @@ module Percheron
78
78
  @stacks = @yaml_contents = @raw_contents = @contents = nil
79
79
  end
80
80
 
81
- def process_stacks! # FIXME: bugs here :(
81
+ # FIXME: bugs here :(
82
+ def process_stacks!
82
83
  stacks_by_name = contents.stacks.to_hash_by_key(:name)
83
84
  scanned = scan_unit_configs(stacks_by_name)
84
85
  stacks_by_name.each do |_, stack|
@@ -103,14 +104,15 @@ module Percheron
103
104
  end
104
105
 
105
106
  def replace_scanned(all, config, scanned)
106
- match = config.fetch(:dependant_unit_names, [])
107
+ match = config.fetch(:needed_unit_names, [])
107
108
  unless (match & scanned.keys).empty?
108
- config.dependant_unit_names = match.map { |v| scanned[v] }.flatten
109
+ config.needed_unit_names = match.map { |v| scanned[v] }.flatten
109
110
  end
110
111
  all[config.name] = config
111
112
  end
112
113
 
113
- def scan_unit_configs(stacks_by_name) # FIXME
114
+ # FIXME
115
+ def scan_unit_configs(stacks_by_name)
114
116
  all = {}
115
117
  stacks_by_name.each do |_, stack|
116
118
  stack.fetch(:units, []).each do |unit_config|
@@ -123,7 +125,8 @@ module Percheron
123
125
  all
124
126
  end
125
127
 
126
- def expand_unit_config(unit_config, new_unit_names) # FIXME
128
+ # FIXME
129
+ def expand_unit_config(unit_config, new_unit_names)
127
130
  new_unit_names.each_with_object({}) do |new_name, all|
128
131
  temp_unit_config = unit_config.dup
129
132
  temp_unit_config.delete(:instances)
@@ -5,5 +5,6 @@ module Percheron
5
5
  class UnitInvalid < StandardError; end
6
6
  class UnitDoesNotExist < StandardError; end
7
7
  class DockerClientInvalid < StandardError; end
8
+ class DockerContainerCannotDelete < StandardError; end
8
9
  end
9
10
  end
@@ -87,7 +87,7 @@ module Percheron
87
87
  unit.ports.each do |ports|
88
88
  label << '<font point-size="11">p: %s, i: %s</font>' % ports.split(':')
89
89
  end
90
- { shape: shape, label: '<%s>' % label.join('<br/>'), fontname: 'arial' }
90
+ { shape: shape, label: '<%s>' % [ label.join('<br/>') ], fontname: 'arial' }
91
91
  end
92
92
 
93
93
  def pseudo_node_opts(unit)
@@ -96,8 +96,8 @@ module Percheron
96
96
 
97
97
  def add_links
98
98
  units.each do |name, unit|
99
- unit.dependant_units.each do |dependant_name, dependant_unit|
100
- graph.add_edges(nodes[name], nodes[dependant_name], node_link_opts(dependant_unit))
99
+ unit.needed_units.each do |needed_name, needed_unit|
100
+ graph.add_edges(nodes[name], nodes[needed_name], node_link_opts(needed_unit))
101
101
  end
102
102
  end
103
103
  end
@@ -108,6 +108,5 @@ module Percheron
108
108
  color = unit.startable? ? 'black' : 'gray'
109
109
  { dir: direction, style: style, color: color }
110
110
  end
111
-
112
111
  end
113
112
  end
@@ -6,7 +6,7 @@ logger_level = Logger::INFO
6
6
  logger_level = Logger::WARN if ENV['QUIET'] == 'true'
7
7
 
8
8
  # :nocov:
9
- if ENV['DEBUG'] == 'true' || ENV['DOCKER_DEBUG'] == 'true'
9
+ if [ ENV['DEBUG'], ENV['DOCKER_DEBUG'] ].include?('true')
10
10
  logger_level = Logger::DEBUG
11
11
  Docker.logger = $logger if ENV['DOCKER_DEBUG'] == 'true'
12
12
  end
@@ -22,7 +22,7 @@ module Percheron
22
22
  end
23
23
 
24
24
  def metastore_key
25
- @metastore_key ||= 'stacks.%s' % name
25
+ @metastore_key ||= 'stacks.%s' % [ name ]
26
26
  end
27
27
 
28
28
  def unit_configs
@@ -30,7 +30,7 @@ module Percheron
30
30
  end
31
31
 
32
32
  def units(unit_names = [])
33
- unit_names = !unit_names.empty? ? unit_names : filter_unit_names
33
+ unit_names = unit_names.empty? ? stack_units.keys : unit_names
34
34
  unit_names.each_with_object({}) do |unit_name, all|
35
35
  all[unit_name] = unit_from_name(unit_name)
36
36
  end
@@ -38,11 +38,11 @@ module Percheron
38
38
 
39
39
  def graph!(file)
40
40
  Graph.new(self).save!(file)
41
- $logger.info "Saved '%s'" % file
41
+ $logger.info "Saved '%s'" % [ file ]
42
42
  end
43
43
 
44
- def shell!(unit_name, command: Percheron::Actions::Shell::DEFAULT_COMMAND)
45
- Actions::Shell.new(unit_from_name(unit_name), command: command).execute!
44
+ def shell!(unit_name, raw_command: Percheron::Actions::Shell::DEFAULT_COMMAND)
45
+ Actions::Shell.new(unit_from_name(unit_name), raw_command: raw_command).execute!
46
46
  end
47
47
 
48
48
  def logs!(unit_name, follow: false)
@@ -50,50 +50,53 @@ module Percheron
50
50
  end
51
51
 
52
52
  def stop!(unit_names: [])
53
+ unit_names = stack_units.keys if unit_names.empty?
53
54
  execute!(Actions::Stop, filter_unit_names(unit_names).reverse)
54
55
  end
55
56
 
56
- # FIXME: bug when non-startable unit specified, all units started
57
57
  def start!(unit_names: [])
58
- unit_names = dependant_units_for(unit_names)
59
- exec_on_dependant_units_for(unit_names) do |unit|
60
- dependant_units = unit.startable_dependant_units.values
61
- Actions::Start.new(unit, dependant_units: dependant_units).execute!
58
+ unit_names = stack_units.keys if unit_names.empty?
59
+ unit_names = needed_units_for(unit_names)
60
+ exec_on_needed_units_for(unit_names) do |unit|
61
+ needed_units = unit.startable_needed_units.values
62
+ Actions::Start.new(unit, needed_units: needed_units).execute!
62
63
  end
63
64
  nil
64
65
  end
65
66
 
66
67
  def restart!(unit_names: [])
68
+ unit_names = stack_units.keys if unit_names.empty?
67
69
  execute!(Actions::Restart, filter_unit_names(unit_names))
68
70
  end
69
71
 
70
- def build!(unit_names: [], forcerm: false)
71
- unit_names = dependant_units_for(unit_names)
72
- exec_on_dependant_units_for(unit_names) do |unit|
73
- Actions::Build.new(unit, forcerm: forcerm).execute!
72
+ def build!(unit_names: [], usecache: true, forcerm: false)
73
+ unit_names = stack_units.keys if unit_names.empty?
74
+ unit_names = needed_units_for(unit_names)
75
+ exec_on_needed_units_for(unit_names) do |unit|
76
+ Actions::Build.new(unit, usecache: usecache, forcerm: forcerm).execute!
74
77
  end
75
78
  nil
76
79
  end
77
80
 
78
- def create!(unit_names: [], start: false)
79
- execute!(Actions::Create, dependant_units_for(unit_names), start: start)
80
- end
81
-
82
- def recreate!(unit_names: [], start: false, force: false)
83
- execute!(Actions::Recreate, filter_unit_names(unit_names), start: start, force: force)
81
+ def create!(unit_names: [], build: true, start: false, deep: false, force: false)
82
+ opts = { build: build, start: start, force: force }
83
+ unit_names = if deep
84
+ unit_names = stack_units.keys if unit_names.empty?
85
+ needed_units_for(unit_names)
86
+ else
87
+ filter_unit_names(unit_names)
88
+ end
89
+ execute!(Actions::Create, unit_names, opts)
84
90
  end
85
91
 
86
92
  def purge!(unit_names: [], force: false)
93
+ unit_names = stack_units.keys if unit_names.empty?
87
94
  execute!(Actions::Purge, filter_unit_names(unit_names).reverse, force: force)
88
95
  end
89
96
 
90
97
  def execute!(klass, unit_names, args=nil)
91
- exec_on_dependant_units_for(unit_names) do |unit|
92
- if args
93
- klass.new(unit, args).execute!
94
- else
95
- klass.new(unit).execute!
96
- end
98
+ exec_on_needed_units_for(unit_names) do |unit|
99
+ args ? klass.new(unit, args).execute! : klass.new(unit).execute!
97
100
  end
98
101
  nil
99
102
  end
@@ -110,20 +113,20 @@ module Percheron
110
113
  @stack_config ||= (config.stacks[stack_name] || Hashie::Mash.new({}))
111
114
  end
112
115
 
113
- # FIXME: yuck
114
- # rubocop:disable Style/Next
116
+ def stack_units
117
+ @stack_units ||= stack_config.fetch('units', {})
118
+ end
119
+
115
120
  def filter_unit_names(unit_names = [])
116
- stack_config.fetch('units', {}).map do |unit_name, unit_config|
117
- if unit_names.empty? || unit_names.include?(unit_name) ||
118
- (unit_config.pseudo_name &&
119
- unit_names.include?(unit_config.pseudo_name))
121
+ stack_units.map do |unit_name, unit_config|
122
+ if unit_names.include?(unit_name) ||
123
+ (unit_config.pseudo_name && unit_names.include?(unit_config.pseudo_name))
120
124
  unit_config.name
121
125
  end
122
- end.compact
126
+ end.compact.uniq
123
127
  end
124
- # rubocop:enable Style/Next
125
128
 
126
- def exec_on_dependant_units_for(unit_names)
129
+ def exec_on_needed_units_for(unit_names)
127
130
  exec_on_units(unit_names) do |unit|
128
131
  $logger.debug "Processing '#{unit.display_name}' unit"
129
132
  yield(unit)
@@ -135,21 +138,21 @@ module Percheron
135
138
  units(unit_names).each { |_, unit| yield(unit) }
136
139
  end
137
140
 
138
- def dependant_units_for(unit_names)
141
+ def needed_units_for(unit_names)
139
142
  list = []
140
143
  unit_names = filter_unit_names(unit_names)
141
- units = all_units_and_dependants(unit_names)
142
- units.each do |unit_name, dependant_unit_names|
143
- list += dependant_unit_names unless dependant_unit_names.empty?
144
+ units = all_units_and_neededs(unit_names)
145
+ units.each do |unit_name, needed_unit_names|
146
+ list += needed_unit_names unless needed_unit_names.empty?
144
147
  list << unit_name
145
148
  end
146
149
  list.uniq
147
150
  end
148
151
 
149
- def all_units_and_dependants(unit_names)
152
+ def all_units_and_neededs(unit_names)
150
153
  all_units = units
151
154
  units = unit_names.each_with_object({}) do |unit_name, all|
152
- all[unit_name] = all_units[unit_name].dependant_unit_names
155
+ all[unit_name] = all_units[unit_name].needed_unit_names
153
156
  end
154
157
  units.sort { |x, y| x[1].length <=> y[1].length } # FIXME
155
158
  end
@@ -0,0 +1,33 @@
1
+ module Percheron
2
+ class Unit
3
+ module ImageHelper
4
+ def image_name
5
+ '%s:%s' % [ image_repo, image_version.to_s ] if image_repo && image_version
6
+ end
7
+
8
+ def image_repo
9
+ if !buildable?
10
+ unit_config.docker_image.split(':')[0]
11
+ elsif pseudo?
12
+ pseudo_full_name
13
+ else
14
+ full_name
15
+ end
16
+ end
17
+
18
+ def image_version
19
+ if buildable?
20
+ unit_config.version
21
+ elsif !unit_config.docker_image.nil?
22
+ unit_config.docker_image.split(':')[1] || 'latest'
23
+ else
24
+ fail Errors::UnitInvalid, 'Cannot determine image version'
25
+ end
26
+ end
27
+
28
+ def image_exists?
29
+ image.nil? ? false : true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,13 +1,15 @@
1
+ require 'percheron/unit/image_helper'
2
+
1
3
  module Percheron
2
4
  class Unit
3
5
 
4
6
  extend Forwardable
5
7
  extend ConfigDelegator
8
+ include Unit::ImageHelper
6
9
 
7
10
  def_delegators :unit_config, :name, :pseudo_name, :docker_image
8
- def_config_item_with_default :unit_config, [], :env, :ports, :volumes,
9
- :dependant_unit_names, :pre_build_scripts,
10
- :post_start_scripts, :start_args, :dns
11
+ def_config_item_with_default :unit_config, [], :env, :ports, :volumes, :needed_unit_names,
12
+ :pre_build_scripts, :post_start_scripts, :start_args, :dns
11
13
  def_config_item_with_default :unit_config, true, :startable
12
14
 
13
15
  def initialize(config, stack, unit_name)
@@ -18,14 +20,14 @@ module Percheron
18
20
  self
19
21
  end
20
22
 
21
- def dependant_units
22
- dependant_unit_names.each_with_object({}) do |unit_name, all|
23
+ def needed_units
24
+ needed_unit_names.each_with_object({}) do |unit_name, all|
23
25
  all[unit_name] = stack.units[unit_name]
24
26
  end
25
27
  end
26
28
 
27
- def startable_dependant_units
28
- dependant_units.select { |_, unit| unit.startable? }
29
+ def startable_needed_units
30
+ needed_units.select { |_, unit| unit.startable? }
29
31
  end
30
32
 
31
33
  def metastore_key
@@ -37,31 +39,19 @@ module Percheron
37
39
  end
38
40
 
39
41
  def hostname
40
- unit_config.fetch('hostname', name)
41
- end
42
-
43
- def image_name
44
- '%s:%s' % [ image_repo, image_version.to_s ] if image_repo && image_version
42
+ unit_config.fetch('hostname', full_name)
45
43
  end
46
44
 
47
- def image_repo
48
- if !buildable?
49
- unit_config.docker_image.split(':')[0]
50
- elsif pseudo?
51
- pseudo_full_name
52
- else
53
- full_name
45
+ def restart_policy
46
+ @restart_policy ||= begin
47
+ name = unit_config.fetch('restart_policy', 'always')
48
+ max_retry_count = unit_config.fetch('restart_policy_retry_count', 0)
49
+ { 'Name' => name, 'MaximumRetryCount' => max_retry_count }
54
50
  end
55
51
  end
56
52
 
57
- def image_version
58
- if buildable?
59
- unit_config.version
60
- elsif !unit_config.docker_image.nil?
61
- unit_config.docker_image.split(':')[1] || 'latest'
62
- else
63
- fail Errors::UnitInvalid, 'Cannot determine image version'
64
- end
53
+ def privileged
54
+ unit_config.fetch('privileged', false)
65
55
  end
66
56
 
67
57
  def full_name
@@ -95,9 +85,7 @@ module Percheron
95
85
  end
96
86
 
97
87
  def links
98
- startable_dependant_units.map do |_, unit|
99
- '%s:%s' % [ unit.full_name, unit.full_name ]
100
- end
88
+ startable_needed_units.map { |_, unit| '%s:%s' % [ unit.full_name, unit.full_name ] }
101
89
  end
102
90
 
103
91
  def container
@@ -141,10 +129,6 @@ module Percheron
141
129
  !info.empty?
142
130
  end
143
131
 
144
- def image_exists?
145
- image.nil? ? false : true
146
- end
147
-
148
132
  def buildable?
149
133
  !dockerfile.nil? && unit_config.docker_image.nil?
150
134
  end
@@ -178,6 +162,5 @@ module Percheron
178
162
  def info
179
163
  Hashie::Mash.new(container.info)
180
164
  end
181
-
182
165
  end
183
166
  end
@@ -1,3 +1,3 @@
1
1
  module Percheron
2
- VERSION = '0.7.16'
2
+ VERSION = '0.8.0'
3
3
  end
data/lib/percheron.rb CHANGED
@@ -7,6 +7,7 @@ require 'semantic'
7
7
  require 'metastore'
8
8
  require 'liquid'
9
9
  require 'singleton'
10
+ require 'securerandom'
10
11
 
11
12
  require 'percheron/oh_dear'
12
13
  require 'percheron/core_extensions'
data/percheron.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_development_dependency 'pry-byebug', '~> 3.2.0'
37
37
  spec.add_development_dependency 'rake', '~> 10.4.0'
38
38
  spec.add_development_dependency 'rspec', '~> 3.3.0'
39
- spec.add_development_dependency 'rubocop', '~> 0.32.0'
39
+ spec.add_development_dependency 'rubocop', '~> 0.33.0'
40
40
  spec.add_development_dependency 'simplecov', '~> 0.10.0'
41
41
  spec.add_development_dependency 'timecop', '~> 0.8.0'
42
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: percheron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.16
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ash McKenzie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-07 00:00:00.000000000 Z
11
+ date: 2015-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clamp
@@ -254,14 +254,14 @@ dependencies:
254
254
  requirements:
255
255
  - - "~>"
256
256
  - !ruby/object:Gem::Version
257
- version: 0.32.0
257
+ version: 0.33.0
258
258
  type: :development
259
259
  prerelease: false
260
260
  version_requirements: !ruby/object:Gem::Requirement
261
261
  requirements:
262
262
  - - "~>"
263
263
  - !ruby/object:Gem::Version
264
- version: 0.32.0
264
+ version: 0.33.0
265
265
  - !ruby/object:Gem::Dependency
266
266
  name: simplecov
267
267
  requirement: !ruby/object:Gem::Requirement
@@ -322,7 +322,6 @@ files:
322
322
  - lib/percheron/actions/exec_local.rb
323
323
  - lib/percheron/actions/logs.rb
324
324
  - lib/percheron/actions/purge.rb
325
- - lib/percheron/actions/recreate.rb
326
325
  - lib/percheron/actions/restart.rb
327
326
  - lib/percheron/actions/shell.rb
328
327
  - lib/percheron/actions/start.rb
@@ -337,7 +336,6 @@ files:
337
336
  - lib/percheron/commands/logs.rb
338
337
  - lib/percheron/commands/main.rb
339
338
  - lib/percheron/commands/purge.rb
340
- - lib/percheron/commands/recreate.rb
341
339
  - lib/percheron/commands/restart.rb
342
340
  - lib/percheron/commands/shell.rb
343
341
  - lib/percheron/commands/start.rb
@@ -358,6 +356,7 @@ files:
358
356
  - lib/percheron/oh_dear.rb
359
357
  - lib/percheron/stack.rb
360
358
  - lib/percheron/unit.rb
359
+ - lib/percheron/unit/image_helper.rb
361
360
  - lib/percheron/validators.rb
362
361
  - lib/percheron/validators/config.rb
363
362
  - lib/percheron/validators/docker_client.rb
@@ -1,51 +0,0 @@
1
- module Percheron
2
- module Actions
3
- class Recreate
4
-
5
- include Base
6
-
7
- def initialize(unit, start: false, force: false)
8
- @unit = unit
9
- @start = start
10
- @force = force
11
- end
12
-
13
- def execute!
14
- results = []
15
- if recreate? || force?
16
- results << recreate!
17
- results << start!
18
- else
19
- inform!
20
- end
21
- results.compact.empty? ? nil : unit
22
- end
23
-
24
- private
25
-
26
- attr_reader :unit, :start, :force
27
- alias_method :start?, :start
28
- alias_method :force?, :force
29
-
30
- def recreate?
31
- !unit.versions_match? || !unit.dockerfile_md5s_match?
32
- end
33
-
34
- def inform!
35
- return nil unless unit.dockerfile_md5s_match?
36
- $logger.info "Unit '#{unit.display_name}' - No Dockerfile changes or version bump"
37
- end
38
-
39
- def recreate!
40
- $logger.debug "Unit '#{unit.display_name}' exists but will be recreated"
41
- Purge.new(unit).execute!
42
- Create.new(unit).execute!
43
- end
44
-
45
- def start!
46
- Start.new(unit).execute! if start?
47
- end
48
-
49
- end
50
- end
51
- end
@@ -1,14 +0,0 @@
1
- module Percheron
2
- module Commands
3
- class Recreate < Abstract
4
-
5
- default_create_parameters!
6
- option([ '-f', '--force' ], :flag, 'Force purge and create', default: false)
7
-
8
- def execute
9
- super
10
- stack.recreate!(unit_names: unit_names, start: start?, force: force?)
11
- end
12
- end
13
- end
14
- end