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.
- checksums.yaml +4 -4
- data/Gemfile +3 -3
- data/Guardfile +5 -0
- data/bin/percheron +8 -4
- data/lib/percheron.rb +3 -0
- data/lib/percheron/actions.rb +15 -0
- data/lib/percheron/actions/base.rb +50 -0
- data/lib/percheron/actions/build.rb +47 -0
- data/lib/percheron/actions/create.rb +109 -0
- data/lib/percheron/actions/exec.rb +51 -0
- data/lib/percheron/actions/purge.rb +38 -0
- data/lib/percheron/actions/recreate.rb +98 -0
- data/lib/percheron/actions/rename.rb +64 -0
- data/lib/percheron/actions/restart.rb +31 -0
- data/lib/percheron/actions/start.rb +46 -0
- data/lib/percheron/actions/stop.rb +27 -0
- data/lib/percheron/cli.rb +1 -0
- data/lib/percheron/cli/abstract_command.rb +7 -0
- data/lib/percheron/cli/create_command.rb +3 -2
- data/lib/percheron/cli/list_command.rb +2 -8
- data/lib/percheron/cli/main_command.rb +1 -0
- data/lib/percheron/cli/purge_command.rb +12 -0
- data/lib/percheron/cli/recreate_command.rb +9 -2
- data/lib/percheron/cli/restart_command.rb +3 -2
- data/lib/percheron/cli/start_command.rb +3 -2
- data/lib/percheron/cli/stop_command.rb +3 -2
- data/lib/percheron/container.rb +120 -5
- data/lib/percheron/errors.rb +0 -1
- data/lib/percheron/formatters/stack/table.rb +1 -1
- data/lib/percheron/null_container.rb +13 -0
- data/lib/percheron/oh_dear.rb +46 -0
- data/lib/percheron/stack.rb +90 -31
- data/lib/percheron/validators/container.rb +9 -1
- data/lib/percheron/version.rb +1 -1
- data/percheron.gemspec +2 -0
- metadata +45 -11
- data/lib/percheron/container/actions.rb +0 -15
- data/lib/percheron/container/actions/base.rb +0 -21
- data/lib/percheron/container/actions/build.rb +0 -58
- data/lib/percheron/container/actions/create.rb +0 -56
- data/lib/percheron/container/actions/recreate.rb +0 -110
- data/lib/percheron/container/actions/start.rb +0 -58
- data/lib/percheron/container/actions/stop.rb +0 -33
- data/lib/percheron/container/main.rb +0 -193
- data/lib/percheron/container/null.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57bb2c76f384e9d0ac932c72d1c607bac65f9bd8
|
4
|
+
data.tar.gz: 4cfa40df1e9fefad4238f6960e2d4570e59a5502
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
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
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::
|
13
|
+
logger_level = Logger::INFO
|
14
14
|
|
15
|
-
if ENV['
|
16
|
-
logger_level = Logger::
|
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
|
-
|
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
|