docker-rails 0.0.2 → 0.1.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/.rspec +2 -0
- data/README.md +190 -74
- data/Rakefile +7 -1
- data/bin/docker-rails +3 -133
- data/docker-rails.gemspec +5 -3
- data/lib/docker/rails.rb +11 -1
- data/lib/docker/rails/CLI/db_check.rb +67 -0
- data/lib/docker/rails/CLI/gems_volume.rb +27 -0
- data/lib/docker/rails/CLI/main.rb +117 -0
- data/lib/docker/rails/app.rb +176 -0
- data/lib/docker/rails/config.rb +40 -0
- data/lib/docker/rails/version.rb +1 -1
- data/spec/docker/rails/config_spec.rb +95 -0
- data/spec/docker/rails/docker-rails.yml +159 -0
- data/spec/spec_helper.rb +11 -0
- metadata +64 -12
- data/bin/docker-rails-db-check +0 -38
data/docker-rails.gemspec
CHANGED
@@ -20,9 +20,11 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
s.add_development_dependency 'bundler', '~> 1.6'
|
22
22
|
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'rspec'
|
24
|
+
s.add_development_dependency 'mysql2', '~> 0.3.18' # http://stackoverflow.com/a/32466950/2363935
|
25
|
+
s.add_development_dependency 'activerecord'
|
23
26
|
|
24
27
|
s.add_dependency 'docker-api'
|
25
|
-
|
26
|
-
s.add_dependency '
|
27
|
-
s.add_dependency 'mysql2', '~> 0.3.18' # http://stackoverflow.com/a/32466950/2363935
|
28
|
+
s.add_dependency 'dry-config', '>= 1.2.6'
|
29
|
+
s.add_dependency 'thor'
|
28
30
|
end
|
data/lib/docker/rails.rb
CHANGED
@@ -5,4 +5,14 @@ module Docker
|
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
8
|
-
require '
|
8
|
+
require 'thor'
|
9
|
+
require 'docker'
|
10
|
+
|
11
|
+
require 'docker/rails/config'
|
12
|
+
require 'docker/rails/compose_config'
|
13
|
+
require 'docker/rails/app'
|
14
|
+
|
15
|
+
require 'docker/rails/cli/db_check'
|
16
|
+
require 'docker/rails/cli/gems_volume'
|
17
|
+
|
18
|
+
require 'docker/rails/cli/main'
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Docker
|
2
|
+
module Rails
|
3
|
+
module CLI
|
4
|
+
require 'thor'
|
5
|
+
class DbCheck < Thor
|
6
|
+
|
7
|
+
default_task :help
|
8
|
+
|
9
|
+
desc 'mysql', 'Ping and wait for mysql database to be up'
|
10
|
+
option :count, default: 60, desc: 'Number of attempts'
|
11
|
+
option :host, default: 'db'
|
12
|
+
option :port, default: 3306
|
13
|
+
option :username, default: 'root'
|
14
|
+
option :password, desc: 'Password-less login if unspecified'
|
15
|
+
|
16
|
+
def mysql
|
17
|
+
|
18
|
+
App.instance
|
19
|
+
|
20
|
+
# ping db to see if it is ready before continuing
|
21
|
+
require 'rubygems'
|
22
|
+
require 'active_record'
|
23
|
+
require 'mysql2'
|
24
|
+
|
25
|
+
puts "\n"
|
26
|
+
connect_string = "#{options[:username]}@#{options[:host]}:#{options[:port]}"
|
27
|
+
printf "Waiting for confirmation of db service startup at #{connect_string}..."
|
28
|
+
last_message = ''
|
29
|
+
loop_limit = options[:count].to_i + 1
|
30
|
+
loop_limit.times do |i|
|
31
|
+
if i == loop_limit - 1
|
32
|
+
printf "failed to connect. #{last_message}\n\n\n"
|
33
|
+
raise "Failed to connect to db service at #{connect_string}. #{last_message}"
|
34
|
+
end
|
35
|
+
|
36
|
+
connection_options = {
|
37
|
+
adapter: 'mysql2',
|
38
|
+
host: options[:host],
|
39
|
+
port: options[:port],
|
40
|
+
username: options[:username]
|
41
|
+
}
|
42
|
+
|
43
|
+
#puts "Password is nil? #{options[:password].nil?}, |#{options[:password]}|"
|
44
|
+
|
45
|
+
connection_options[:password] = options[:password] unless options[:password].nil?
|
46
|
+
|
47
|
+
ActiveRecord::Base.establish_connection (connection_options)
|
48
|
+
connected =
|
49
|
+
begin
|
50
|
+
ActiveRecord::Base.connection_pool.with_connection { |con| con.active? }
|
51
|
+
rescue => e
|
52
|
+
last_message = "#{e.class.name}: #{e.message}"
|
53
|
+
false
|
54
|
+
end
|
55
|
+
printf '.'
|
56
|
+
if connected
|
57
|
+
printf 'connected.'
|
58
|
+
break
|
59
|
+
end
|
60
|
+
sleep 1
|
61
|
+
end
|
62
|
+
puts "\n"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Docker
|
2
|
+
module Rails
|
3
|
+
module CLI
|
4
|
+
class GemsVolume < Thor
|
5
|
+
|
6
|
+
default_task :help
|
7
|
+
|
8
|
+
desc 'create', 'Create a gem volume'
|
9
|
+
def create(build_name = nil, environment_name = nil)
|
10
|
+
# Create global gems data volume to cache gems for this version of ruby
|
11
|
+
app = App.configured(build_name, environment_name)
|
12
|
+
begin
|
13
|
+
Docker::Container.get(app.gems_volume_name)
|
14
|
+
puts "Gem data volume container #{app.gems_volume_name} already exists."
|
15
|
+
rescue Docker::Error::NotFoundError => e
|
16
|
+
|
17
|
+
exec "docker create -v #{app.gems_volume_path} --name #{app.gems_volume_path} busybox"
|
18
|
+
puts "Gem data volume container #{app.gems_volume_name} created."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# TODO: add destroy volume
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Docker
|
2
|
+
module Rails
|
3
|
+
module CLI
|
4
|
+
class Main < Thor
|
5
|
+
# default_task :help
|
6
|
+
|
7
|
+
desc 'db_check <db>', 'Runs db_check'
|
8
|
+
subcommand 'db_check', Docker::Rails::CLI::DbCheck
|
9
|
+
|
10
|
+
|
11
|
+
desc 'gems_volume <command>', 'Gems volume management'
|
12
|
+
subcommand 'gems_volume', Docker::Rails::CLI::GemsVolume
|
13
|
+
|
14
|
+
|
15
|
+
desc 'ci <build_name> <environment_name>', 'Execute the works, everything with cleanup included i.e. bundle exec docker-rails ci 222 test'
|
16
|
+
long_desc <<-D
|
17
|
+
|
18
|
+
`ci` will run the targeted environment_name with the given build number then cleanup everything upon completion.
|
19
|
+
While it is named `ci`, there is no harm in using this for other environments as long as you understand that volumes
|
20
|
+
and remaining dangling images will be cleaned up upon completion.
|
21
|
+
D
|
22
|
+
|
23
|
+
def ci(build_name, environment_name)
|
24
|
+
invoke :compose
|
25
|
+
invoke CLI::GemsVolume, :create
|
26
|
+
invoke :before
|
27
|
+
begin
|
28
|
+
invoke :up
|
29
|
+
ensure
|
30
|
+
invoke :stop
|
31
|
+
invoke :rm_volumes
|
32
|
+
invoke :rm_compose
|
33
|
+
invoke :rm_dangling
|
34
|
+
invoke :show_all_containers
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'compose <build_name> <environment_name>', 'Writes a resolved docker-compose.yml file'
|
39
|
+
|
40
|
+
def compose(build_name, environment_name)
|
41
|
+
App.configured(build_name, environment_name).compose
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'before <build_name> <environment_name>', 'Invoke before_command', hide: true
|
45
|
+
|
46
|
+
def before(build_name, environment_name)
|
47
|
+
invoke :compose
|
48
|
+
App.configured(build_name, environment_name).exec_before_command
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'up <build_name> <environment_name>', 'Up the docker-compose configuration for the given build_name/environment_name'
|
52
|
+
|
53
|
+
def up(build_name, environment_name)
|
54
|
+
|
55
|
+
invoke CLI::GemsVolume, :create
|
56
|
+
invoke :before
|
57
|
+
App.configured(build_name, environment_name).exec_up
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'stop <build_name> <environment_name>', 'Stop all running containers for the given build_name/environment_name'
|
61
|
+
|
62
|
+
def stop(build_name, environment_name)
|
63
|
+
invoke :compose
|
64
|
+
App.configured(build_name, environment_name).exec_stop
|
65
|
+
end
|
66
|
+
|
67
|
+
desc 'rm_volumes <build_name> <environment_name>', 'Stop all running containers and remove corresponding volumes for the given build_name/environment_name'
|
68
|
+
|
69
|
+
def rm_volumes(build_name, environment_name)
|
70
|
+
invoke :stop
|
71
|
+
App.configured(build_name, environment_name).exec_remove_volumes
|
72
|
+
end
|
73
|
+
|
74
|
+
desc 'rm_compose', 'Remove generated docker_compose file'
|
75
|
+
|
76
|
+
def rm_compose(build_name = nil, environment_name = nil)
|
77
|
+
App.instance.rm_compose
|
78
|
+
end
|
79
|
+
|
80
|
+
desc 'rm_dangling', 'Remove danging images'
|
81
|
+
|
82
|
+
def rm_dangling(build_name = nil, environment_name = nil)
|
83
|
+
App.instance.rm_dangling
|
84
|
+
end
|
85
|
+
|
86
|
+
desc 'show_all_containers', 'Show all remaining containers regardless of state'
|
87
|
+
|
88
|
+
def show_all_containers(build_name = nil, environment_name = nil)
|
89
|
+
App.instance.show_all_containers
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# desc 'hello NAME', 'This will greet you'
|
94
|
+
# long_desc <<-HELLO_WORLD
|
95
|
+
#
|
96
|
+
# `hello NAME` will print out a message to the person of your choosing.
|
97
|
+
#
|
98
|
+
# Brian Kernighan actually wrote the first "Hello, World!" program
|
99
|
+
# as part of the documentation for the BCPL programming language
|
100
|
+
# developed by Martin Richards. BCPL was used while C was being
|
101
|
+
# developed at Bell Labs a few years before the publication of
|
102
|
+
# Kernighan and Ritchie's C book in 1972.
|
103
|
+
#
|
104
|
+
# http://stackoverflow.com/a/12785204
|
105
|
+
# HELLO_WORLD
|
106
|
+
#
|
107
|
+
# option :upcase
|
108
|
+
#
|
109
|
+
# def hello(name)
|
110
|
+
# greeting = "Hello, #{name}"
|
111
|
+
# greeting.upcase! if options[:upcase]
|
112
|
+
# puts greeting
|
113
|
+
# end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module Docker
|
2
|
+
module Rails
|
3
|
+
require 'singleton'
|
4
|
+
class App
|
5
|
+
include Singleton
|
6
|
+
attr_reader :config, :compose_config, :ruby_version, :build_name, :environment_name, :gems_volume_path, :gems_volume_name, :compose_filename
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def configured(build_name, environment_name)
|
10
|
+
app = App.instance
|
11
|
+
if app.is_configured?
|
12
|
+
puts "Already configured"
|
13
|
+
else
|
14
|
+
app.configure(build_name: build_name, environment_name: environment_name)
|
15
|
+
end
|
16
|
+
app
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
discover_ruby_version
|
22
|
+
set_gems_volume_vars
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure(options)
|
26
|
+
ENV['BUILD_NAME'] = @build_name = options[:build_name]
|
27
|
+
@environment_name = options[:environment_name]
|
28
|
+
|
29
|
+
# load the docker-rails.yml
|
30
|
+
@config = Docker::Rails::Config.new
|
31
|
+
@config.load!(@environment_name)
|
32
|
+
|
33
|
+
@is_configured = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_configured?
|
37
|
+
@is_configured || false
|
38
|
+
end
|
39
|
+
|
40
|
+
def compose
|
41
|
+
# Write a docker-compose.yml with interpolated variables
|
42
|
+
@compose_filename = compose_filename_from @build_name, @environment_name
|
43
|
+
|
44
|
+
rm_compose
|
45
|
+
|
46
|
+
@config.write_docker_compose_file(@compose_filename)
|
47
|
+
|
48
|
+
@compose_config = Docker::Rails::ComposeConfig.new
|
49
|
+
@compose_config.load!(nil, @compose_filename)
|
50
|
+
end
|
51
|
+
|
52
|
+
def rm_compose
|
53
|
+
# Delete old docker compose files
|
54
|
+
exec "rm #{compose_filename_from '*', '*'}" rescue ''
|
55
|
+
end
|
56
|
+
|
57
|
+
def exec_before_command
|
58
|
+
before_command = @config['before_command']
|
59
|
+
(exec before_command unless before_command.nil?) #unless skip? :before_command
|
60
|
+
end
|
61
|
+
|
62
|
+
def exec_up
|
63
|
+
# Run the compose configuration
|
64
|
+
exec_compose 'up' #unless skip? :up
|
65
|
+
end
|
66
|
+
|
67
|
+
def exec_stop
|
68
|
+
puts "\n\n\n\nStopping containers..."
|
69
|
+
puts '-----------------------------'
|
70
|
+
@compose_config.each_key do |service_name|
|
71
|
+
stop(service_name)
|
72
|
+
end
|
73
|
+
# puts "\nDone."
|
74
|
+
end
|
75
|
+
|
76
|
+
def exec_remove_volumes
|
77
|
+
puts "\n\nRemoving container volumes..."
|
78
|
+
puts '-----------------------------'
|
79
|
+
@compose_config.each_key do |service_name|
|
80
|
+
rm_v(service_name)
|
81
|
+
end
|
82
|
+
# puts "\nDone."
|
83
|
+
end
|
84
|
+
|
85
|
+
def rm_dangling
|
86
|
+
puts "\n\nCleaning up dangling images..."
|
87
|
+
puts '-----------------------------'
|
88
|
+
exec 'docker images --filter dangling=true -q | xargs docker rmi'
|
89
|
+
# puts "\nDone."
|
90
|
+
end
|
91
|
+
|
92
|
+
def show_all_containers
|
93
|
+
puts "\n\nAll remaining containers..."
|
94
|
+
puts '-----------------------------'
|
95
|
+
system 'docker ps -a'
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
def exec(cmd, capture = false)
|
101
|
+
puts "Running `#{cmd}`" if verbose?
|
102
|
+
if capture
|
103
|
+
output = %x[#{cmd}]
|
104
|
+
else
|
105
|
+
system cmd
|
106
|
+
end
|
107
|
+
|
108
|
+
raise "Failed to execute: `#{cmd}`" unless $?.success?
|
109
|
+
output
|
110
|
+
end
|
111
|
+
|
112
|
+
# convenience to execute docker-compose with file and project params
|
113
|
+
def exec_compose(cmd, capture = false)
|
114
|
+
exec("docker-compose -f #{@compose_filename} -p #{App.instance.build_name} #{cmd}", capture)
|
115
|
+
end
|
116
|
+
|
117
|
+
# service_name i.e. 'db' or 'web'
|
118
|
+
def get_container_name(service_name)
|
119
|
+
output = exec_compose "ps #{service_name}", true
|
120
|
+
# puts "get_container(#{service_name}): \n#{output}"
|
121
|
+
output =~ /^(\w+)/ # grab the name, only thing that is at the start of the line
|
122
|
+
$1
|
123
|
+
end
|
124
|
+
|
125
|
+
# def up_service(service_name, options = '')
|
126
|
+
# exec_compose "up #{options} #{service_name}"
|
127
|
+
# container_name = get_container_name(service_name)
|
128
|
+
# puts "#{service_name}: container_name #{container_name}"
|
129
|
+
#
|
130
|
+
# container = Docker::Container.get(container_name)
|
131
|
+
# # container.streaming_logs(stdout: true) { |stream, chunk| puts "#{service_name}: #{chunk}" }
|
132
|
+
# # puts container
|
133
|
+
#
|
134
|
+
# {service_name => {'container' => container, 'container_name' => container_name}}
|
135
|
+
# end
|
136
|
+
|
137
|
+
def rm_v(service_name)
|
138
|
+
exec_compose "rm -v --force #{service_name}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def stop(service_name)
|
142
|
+
exec_compose "stop #{service_name}"
|
143
|
+
end
|
144
|
+
|
145
|
+
# def skip?(command)
|
146
|
+
# skips = @config[:skip]
|
147
|
+
# return false if skips.nil?
|
148
|
+
# skip = skips.include? command.to_s
|
149
|
+
# puts "Skipping #{command}" if skip && verbose?
|
150
|
+
# skip
|
151
|
+
# end
|
152
|
+
|
153
|
+
|
154
|
+
def verbose?
|
155
|
+
@verbose ||= (@config['verbose'] unless @config.nil?) || false
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_gems_volume_vars
|
159
|
+
# Set as variable for interpolation
|
160
|
+
ENV['GEMS_VOLUME_PATH'] = @gems_volume_path = "/gems/#{@ruby_version}"
|
161
|
+
ENV['GEMS_VOLUME_NAME'] = @gems_volume_name = "gems-#{@ruby_version}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def discover_ruby_version
|
165
|
+
# Discover ruby version from the Dockerfile image
|
166
|
+
IO.read('Dockerfile') =~ /^FROM \w+\/ruby:(\d+.\d+(?:.\d+))/
|
167
|
+
@ruby_version = $1
|
168
|
+
end
|
169
|
+
|
170
|
+
# accessible so that we can delete patterns
|
171
|
+
def compose_filename_from(build_name, environment_name)
|
172
|
+
"docker-compose-build-#{build_name}-#{environment_name}.yml"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Docker
|
2
|
+
module Rails
|
3
|
+
require 'dry/config'
|
4
|
+
class Config < Dry::Config::Base
|
5
|
+
def initialize(options = {})
|
6
|
+
super({
|
7
|
+
default_configuration: {
|
8
|
+
verbose: false
|
9
|
+
|
10
|
+
},
|
11
|
+
prune: [:development, :test, :parallel_tests, :staging, :production]
|
12
|
+
}.merge(options))
|
13
|
+
end
|
14
|
+
|
15
|
+
def load!(environment, *filenames)
|
16
|
+
if environment.nil?
|
17
|
+
puts 'Environment unspecified, generating docker-compose.yml based on root docker-compose yaml key.'
|
18
|
+
environment = 'docker-compose'
|
19
|
+
end
|
20
|
+
|
21
|
+
if filenames.empty?
|
22
|
+
puts 'Using docker-rails.yml'
|
23
|
+
filenames = ['docker-rails.yml']
|
24
|
+
end
|
25
|
+
|
26
|
+
super(environment, *filenames)
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_docker_compose_file(output_filename = 'docker-compose.yml')
|
30
|
+
write_yaml_file(output_filename, self[:'docker-compose'])
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_yaml(config = @configuration)
|
34
|
+
yaml = super(config)
|
35
|
+
yaml = yaml.gsub(/command: .$/, 'command: >')
|
36
|
+
yaml
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|