docker-rails-app 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 89604320070bdd27a69e302b9ab8293462a8bbd7
4
+ data.tar.gz: 4e633b7d7f29fc3f07233be921023285f967178b
5
+ SHA512:
6
+ metadata.gz: b0df7c6014209c36622f3845abdd89a8cff2c1f7f2b3f98773626795dafd9dc6713f0183eb336c5248aaee242591d7aaba1735bf4e1305841742f4542995dea1
7
+ data.tar.gz: 2a0ea6f57497d5702589e3fa8b7bde7a8d8094bc58c0ecfa2c58a6390f2e994c6586a5fb084039634c5a0090ce641275768163f361066a87eb464b459affa77e
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "docker_rails_app"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,9 @@
1
+ require 'docker_rails_app'
2
+ require 'rails'
3
+
4
+ spec = Gem::Specification.find_by_name 'docker-rails-app'
5
+
6
+ Dir.glob("#{spec.gem_dir}/lib/tasks/**/*.rake").each { |file|
7
+ load file
8
+ }
9
+
@@ -0,0 +1,3 @@
1
+ module DockerRailsApp
2
+ VERSION = "0.3.6"
3
+ end
@@ -0,0 +1,5 @@
1
+ require_relative "./support/docker_runner"
2
+ require_relative "./support/docker_rails_app_creator"
3
+ require "docker_rails_app/version"
4
+
5
+ require "docker_rails_app/import_gem_rake_tasks"
@@ -0,0 +1,81 @@
1
+ # DOCKER_URL=tcp://qa2:4243 ruby lib/docker-api/docker_api_tests.rb
2
+ # DOCKER_URL=tcp://192.168.99.100:2376 ruby lib/docker-api/docker_api_tests.rb
3
+ require 'docker'
4
+
5
+ raise "Remove this monkey patch when API library provides this stats method" if (Docker::Container.respond_to? "stats")
6
+ class Docker::Container
7
+ # Non streaming stats
8
+ def stats(options = {})
9
+ path = path_for(:stats)
10
+ puts "Getting stats from: #{path}"
11
+ options[:stream] = false
12
+ container_stats = nil
13
+ begin
14
+ status = Timeout::timeout(5) {
15
+ container_stats = connection.get(path, options)
16
+ }
17
+ rescue Exception => e
18
+ return {error: e.message}
19
+ end
20
+ JSON.parse(container_stats)
21
+ end
22
+ end
23
+
24
+ class DockerApi
25
+ attr_accessor :connection
26
+
27
+ # Build a connection to docker on a remote host. Need a connection to be able to make multiple docker calls within the app to different hosts.
28
+ # host: {address: "olex-qa2.openlogic.com", docker_port: 4245 }
29
+ def self.connection(host)
30
+ cert_path = ENV['DOCKER_CERT_PATH']
31
+ scheme = "http"
32
+ scheme = "https" if (ENV['DOCKER_TLS_VERIFY'] == "1")
33
+
34
+ docker_connection_opts = {:client_cert=>"#{cert_path}/cert.pem", :client_key=>"#{cert_path}/key.pem",
35
+ :ssl_ca_file=>"#{cert_path}/ca.pem", :scheme => scheme}
36
+
37
+ # docker_connection_opts = {:client_cert=>"/Users/mikemoore/.docker/machine/machines/dev/cert.pem", :client_key=>"/Users/mikemoore/.docker/machine/machines/dev/key.pem",
38
+ # :ssl_ca_file=>"/Users/mikemoore/.docker/machine/machines/dev/ca.pem", :scheme=>"https"}
39
+ docker_connection_opts[:scheme] = "http" if (host[:ssl] == false)
40
+ docker_connection = Docker::Connection.new("tcp://#{host[:address]}:#{host[:docker_port]}", docker_connection_opts)
41
+ return docker_connection
42
+ end
43
+
44
+ def initialize(url, secure = true)
45
+ # Docker.logger = Logger.new(STDOUT)
46
+ # Docker.logger.level = Logger::DEBUG
47
+ address = url.split("//").last.split(":").first
48
+ port = url.split(":").last
49
+ host = {address: address, docker_port: port, ssl: secure}
50
+ @connection = DockerApi.connection(host)
51
+ end
52
+
53
+ def find_container_with_name(wanted_name)
54
+ options = {
55
+ all: true
56
+ }
57
+ all_containers = Docker::Container.all(options, @connection)
58
+ all_containers.each do |container|
59
+ json = container.json
60
+ name = json['Name']
61
+ return container if (name == "/#{wanted_name}")
62
+ end
63
+ return nil
64
+ end
65
+ end
66
+
67
+
68
+ #
69
+ # puts ""
70
+ # puts "Here are Containers on #{docker_url}"
71
+ # all_containers = Docker::Container.all
72
+ # all_containers.each do |container|
73
+ # puts "#{container.json['Name']}"
74
+ # puts " Running: #{container.json['State']['Running']}"
75
+ # puts " Running: #{container.json['State']['ExitCode']}"
76
+ #
77
+ # env_vars = container.json['Config']['Env']
78
+ # env_vars.each do |env|
79
+ # puts " Env: #{env}"
80
+ # end
81
+ # end
@@ -0,0 +1,244 @@
1
+ require_relative "./docker_runner"
2
+ require_relative "./docker_registry"
3
+
4
+ class DockerImageCreator
5
+ attr_accessor :use_date_as_version
6
+
7
+ REMOVE_INTERMEDIATE_FLAG = "--rm=true"
8
+ DAEMON_OPTIONS = " -d -t -P"
9
+ INTERACT_OPTIONS = " -a stdin -a stdout -i -t -P"
10
+ DOCKER_DIR = Dir.pwd + "/lib"
11
+
12
+ attr_accessor :version_forced, :image_name_forced, :docker_host, :docker_host_secure
13
+ attr_reader :definition
14
+
15
+ def initialize(definition)
16
+ raise "Definition is nil" if definition == nil
17
+ @definition = definition
18
+ @home = FileUtils.pwd
19
+ @version = nil
20
+ @docker_host = nil
21
+ @docker_host_secure = nil
22
+ end
23
+
24
+ def use_date_as_version?
25
+ false
26
+ end
27
+
28
+ def image_name
29
+ return (@image_name_forced) if (@image_name_forced)
30
+ build_def = @definition[:build]
31
+ name = @definition[:run][:image_name] if (@definition[:run])
32
+ name = build_def[:name] if (build_def)
33
+ return name
34
+ end
35
+
36
+ def docker_api
37
+ @docker_host = @docker_host || ENV['DOCKER_HOST']
38
+ @docker_host_secure = @docker_host_secure || (ENV['DOCKER_TLS_VERIFY'].to_s == '1')
39
+ raise "Need to define DOCKER_HOST env variable" if (!@docker_host)
40
+ raise "Need to define DOCKER_TLS_VERIFY env variable" if (@docker_host_secure == nil)
41
+ docker_api = DockerApi.new(@docker_host, @docker_host_secure)
42
+ end
43
+
44
+ def build_it()
45
+ docker_connection = docker_api.connection
46
+ build_def = @definition[:build]
47
+ if (build_def == nil)
48
+ puts "Warning: build not defined for #{definition[:code]} - ignoring request"
49
+ else
50
+ begin
51
+ before_build(@definition)
52
+ remove_intermediate = REMOVE_INTERMEDIATE_FLAG
53
+
54
+ volume_path = build_def[:volume_path]
55
+ build_command = nil
56
+
57
+ image_and_version = image_name()
58
+ image_and_version += ":#{version()}" if (version() != nil)
59
+
60
+ opts = build_def[:api_options] || {}
61
+ opts[:t] = "#{image_and_version}"
62
+ opts[:rm] = true
63
+
64
+ Excon.defaults[:write_timeout] = 1000
65
+ Excon.defaults[:read_timeout] = 1000
66
+ puts "Building image. Docker dir: #{docker_dir} ..."
67
+
68
+ image = Docker::Image.build_from_dir(docker_dir, opts, docker_connection) do |v|
69
+ begin
70
+ if (log = JSON.parse(v)) && log.has_key?("stream")
71
+ $stdout.puts log["stream"]
72
+ end
73
+ rescue Exception => ex
74
+ puts "#{v}"
75
+ end
76
+ end
77
+ rescue Exception => ex
78
+ puts"Exception: #{ex.message}"
79
+ puts ex.backtrace
80
+ debugger
81
+ raise "Exception building image. #{ex.message}\nCheck connection to: #{docker_connection}"
82
+ ensure
83
+ after_build(@definition)
84
+ end
85
+ end
86
+ end
87
+
88
+ def version
89
+ return (@version_forced) if (@version_forced)
90
+ build_def = @definition[:build]
91
+ raise "No build definition for: #{@definition[:code]}" if (!build_def)
92
+ version = 'latest'
93
+ version = build_def[:version] if (build_def[:version] != nil)
94
+ return version
95
+ end
96
+
97
+ def tag_it(registry)
98
+ raise "DOCKER_REGISTRY environment variable not defined for push operation" if registry == nil
99
+ do_tag(registry)
100
+ end
101
+
102
+ #def do_tag(image_name, version, registry)
103
+ def do_tag(registry)
104
+ docker_connection = docker_api.connection
105
+ image = Docker::Image.get("#{image_name}:#{version}", {}, docker_connection)
106
+ puts "Tagging image: #{image_name}"
107
+ image.tag('repo' => "#{registry}/#{image_name()}", 'image' => 'unicorn', 'tag' => version(), force: true)
108
+ end
109
+
110
+ def push_it(registry)
111
+ raise "DOCKER_REGISTRY environment variable not defined for push operation" if registry == nil
112
+ build_def = @definition[:build]
113
+ do_push(registry)
114
+ end
115
+
116
+ def api_image(registry, docker_connection)
117
+ full_name = "#{registry}/#{image_name}"
118
+ full_name += ":#{version()}" if (version() != nil)
119
+ image = Docker::Image.get(full_name, {}, docker_connection)
120
+ puts "Pushing Image: #{full_name} ..."
121
+ return image
122
+ end
123
+
124
+ def do_push(registry)
125
+ if (registry)
126
+ Excon.defaults[:write_timeout] = 1000
127
+ Excon.defaults[:read_timeout] = 1000
128
+
129
+ docker_connection = docker_api.connection
130
+
131
+ image = api_image(registry, docker_connection)
132
+
133
+ credentials = nil
134
+ result = image.push(credentials, {tag: version, repo: registry})
135
+ puts "Done pushing Image: #{registry}/#{image_name}"
136
+ the_registry = DockerRegistry.new(registry)
137
+
138
+ if (the_registry.has_image_with_version?("#{image_name}", version) == false)
139
+ raise "After push, image doesn't appear in registry. Image: #{image_name}:#{version}. Push result: #{result}"
140
+ end
141
+ else
142
+ puts "No registry specified for pushing"
143
+ end
144
+ end
145
+
146
+ def run_it(host, host_ssl, interact, run_instance_count, registry = nil)
147
+ @docker_host = host
148
+ @docker_host_secure = host_ssl
149
+ puts "Warning: registry not defined, assuming run is local" if registry == nil
150
+ run_def = @definition[:run]
151
+ build_def = @definition[:build]
152
+ if (run_def == nil)
153
+ puts "No run definition for: #{@definition[:code]}"
154
+ else
155
+ (1..run_instance_count).each do |instance_index|
156
+ use_extension = (run_instance_count > 1)
157
+ run_with_extension(:next, registry, interact, use_extension)
158
+ end
159
+ end
160
+ end
161
+
162
+ def run_with_extension(instance_index, registry, interact, use_extension)
163
+ @docker_api = DockerApi.new(@docker_host)
164
+ docker_runner = DockerRunner.new(self, nil)
165
+ docker_runner.registry = registry
166
+ docker_runner.instance_index = instance_index
167
+ docker_runner.use_extension = use_extension
168
+ docker_runner.interact = interact
169
+ docker_runner.image_name = image_name
170
+ docker_runner.code = @definition[:code]
171
+ docker_runner.remote_address = @docker_host
172
+ docker_runner.secure_docker_api = @docker_host_secure
173
+ docker_runner.run_with_extension
174
+ end
175
+
176
+ def add_it(host, host_ssl, run_instance_count, registry)
177
+ @docker_host = host
178
+ @docker_host_secure = host_ssl
179
+ number_added = 0
180
+ while (number_added < run_instance_count)
181
+ run_def = @definition[:run]
182
+ run_name = run_def[:name]
183
+ extension = 1
184
+ run_with_extension(:next, registry, false, true)
185
+ number_added += 1
186
+ end
187
+ end
188
+
189
+ def kill_all(host, host_ssl)
190
+ docker_runner = DockerRunner.new(self, nil)
191
+ docker_runner.registry = nil
192
+ docker_runner.use_extension = true
193
+ docker_runner.interact = false
194
+ docker_runner.remote_address = host
195
+ docker_runner.secure_docker_api = host_ssl
196
+
197
+ run_def = @definition[:run]
198
+ run_name = run_def[:name]
199
+ docker_runner.kill_all(run_name)
200
+ end
201
+
202
+ def subtract(host, host_ssl, count)
203
+ docker_runner = DockerRunner.new(self, nil)
204
+ docker_runner.registry = nil
205
+ docker_runner.use_extension = true
206
+ docker_runner.interact = false
207
+ docker_runner.remote_address = host
208
+ docker_runner.secure_docker_api = host_ssl
209
+
210
+ run_def = @definition[:run]
211
+ run_name = run_def[:name]
212
+ docker_runner.kill(run_name, count)
213
+ end
214
+
215
+ def openlogic_home()
216
+ @openlogic_home = @openlogic_home[0..(@openlogic_home.length - 2)] if (@openlogic_home.end_with? "/")
217
+ return @openlogic_home
218
+ end
219
+
220
+ def before_build(definition)
221
+ delete_image_support_from_stage()
222
+ copy_image_support_to_stage()
223
+ end
224
+
225
+ def after_build(definition)
226
+ delete_image_support_from_stage()
227
+ end
228
+
229
+ def delete_image_support_from_stage()
230
+ FileUtils.remove_dir("#{docker_dir}image_support", true)
231
+ end
232
+
233
+ def docker_dir
234
+ return @definition[:build][:docker_directory] + "/"
235
+ end
236
+
237
+ def copy_image_support_to_stage
238
+ raise "Definition nil" if @definition == nil
239
+ raise "Build Definition nil" if @definition[:build] == nil
240
+ image_support_dir = "#{FileUtils.pwd}/lib/support/image_support/"
241
+ FileUtils.cp_r(image_support_dir, docker_dir())
242
+ end
243
+
244
+ end
@@ -0,0 +1,60 @@
1
+ require_relative "./docker_image_creator"
2
+ require 'fileutils'
3
+
4
+ class DockerRailsAppCreator < DockerImageCreator
5
+ ARCHIVE_TMP_FILE = "/tmp/rails_application.tar.gz"
6
+
7
+ def docker_dir
8
+ dir = definition['build']['docker_dir'] || "#{FileUtils.pwd}/docker"
9
+ end
10
+
11
+ def before_build(definition)
12
+ build_from_current_dir = true
13
+ stage_files_dir = docker_dir() + "/files"
14
+ FileUtils.rm_rf stage_files_dir
15
+ FileUtils.mkdir_p stage_files_dir
16
+
17
+ archive_file = ARCHIVE_TMP_FILE
18
+ `rm -rf #{archive_file}`
19
+ `tar -czf #{archive_file} --exclude=tmp/* --exclude .git --exclude "*.log" *`
20
+ FileUtils.mv(archive_file, stage_files_dir)
21
+
22
+ create_database_config_file(definition, stage_files_dir)
23
+ end
24
+
25
+ def create_database_config_file(definition, directory)
26
+ db_host_name = definition['run']['name'] + "-db"
27
+ File.open("#{directory}/database.yml", "w") do |file|
28
+ content = "
29
+ #This file generated by Docker Rails when building docker image
30
+ production:
31
+ adapter: mysql2
32
+ database: application_db
33
+ pool: 5
34
+ timeout: 5000
35
+ host: #{db_host_name}
36
+ port: 3306
37
+ user: root
38
+ password: password
39
+ persistence: :db
40
+ "
41
+ file.write(content)
42
+ end
43
+ end
44
+
45
+ def prepare_for_git_pull
46
+ ssh_staging_dir = stage_files_dir + "/ssh/"
47
+ FileUtils.mkdir_p ssh_staging_dir
48
+ FileUtils.cp "#{ENV['HOME']}/.ssh/id_rsa", ssh_staging_dir
49
+ FileUtils.cp "#{ENV['HOME']}/.ssh/id_rsa.pub", ssh_staging_dir
50
+ end
51
+
52
+ def after_build(definiton)
53
+ if (build_source == :current)
54
+ stage_files_dir = docker_dir() + "/files"
55
+ FileUtils.rm_f stage_files_dir
56
+ end
57
+ end
58
+
59
+
60
+ end
@@ -0,0 +1,26 @@
1
+
2
+ class DockerRailsConfigReader
3
+ attr_accessor :definitions
4
+
5
+ def initialize
6
+ @definitions = nil
7
+ config_file = "#{FileUtils.pwd}/docker/docker-rails.json"
8
+ raise "Could not find your project config at: #{config_file} . Did you run: 'rake docker:install' ?" if (!File.exist?(config_file))
9
+ File.open(config_file, "r") do |file|
10
+ str = file.read
11
+ @definitions = JSON.parse(str)
12
+ end
13
+ end
14
+
15
+ def rails_app_configuration
16
+ @definitions.each do |definition|
17
+ name = definition.keys.first
18
+ if (name == "rails_app")
19
+ return definition
20
+ end
21
+ end
22
+ return nil
23
+ end
24
+
25
+
26
+ end
@@ -0,0 +1,58 @@
1
+ require 'json'
2
+
3
+ class DockerRegistry
4
+
5
+ def initialize(registry_url)
6
+ @server = registry_url
7
+ if (!registry_url.include? "://")
8
+ registry_url = "https://#{registry_url}"
9
+ end
10
+ @connection = connection(registry_url)
11
+ end
12
+
13
+ def find_images()
14
+ response = @connection.get "/v2/_catalog"
15
+ raise "catalog retrieval return error: #{response.status} #{response.body}" if (response.status != 200)
16
+ json = JSON.parse(response.body)
17
+ return json['repositories']
18
+ end
19
+
20
+ def find_tags(image_name)
21
+ response = @connection.get "/v2/#{image_name}/tags/list"
22
+ raise "Querying tags returned error code: #{response.status}" if (response.status != 200)
23
+ json = JSON.parse(response.body)
24
+ return json['tags']
25
+ end
26
+
27
+ def has_image_with_version?(image, commit_id)
28
+ tags = self.find_tags(image)
29
+ if (tags != nil)
30
+ tags.each do |tag|
31
+ return true if (tag == commit_id)
32
+ end
33
+ end
34
+ return false
35
+ end
36
+
37
+ def images_and_versions
38
+ hash = {}
39
+ images = find_images()
40
+ images.each do |image|
41
+ tags = find_tags(image)
42
+ hash[image] = tags
43
+ end
44
+ return hash
45
+ end
46
+
47
+ private
48
+
49
+ def connection(address)
50
+ if (!@service_connection)
51
+ @service_connection = Faraday.new address, :ssl => {:verify => false}
52
+ end
53
+ return @service_connection
54
+ end
55
+
56
+
57
+
58
+ end
@@ -0,0 +1,263 @@
1
+ require_relative "launch_support"
2
+ require_relative "./docker_api"
3
+ require 'pp'
4
+
5
+ class DockerRunner
6
+ include LaunchSupport
7
+ attr_accessor :instance_index, :registry, :use_extension, :interact, :image_name, :code
8
+ attr_accessor :host, :user, :deployment_id, :version, :secure_docker_api, :remote_address
9
+ attr_accessor :docker_host, :docker_host_secure, :run_untagged
10
+
11
+
12
+ def initialize(config)
13
+ @config = config
14
+ @run_config = config['run']
15
+ @build_config = config['build']
16
+ @run_untagged = false
17
+
18
+ @image_name = @run_config['image_name']
19
+ if (!@image_name)
20
+ if (@build_config)
21
+ @image_name = @build_config['name']
22
+ raise "Build config does not have 'name'" if (@image_name == nil)
23
+ else
24
+ raise "Can't find an image name. Either specify 'name' in build config or 'image_name' in run config."
25
+ end
26
+ end
27
+
28
+ @registry = @config['registry']
29
+
30
+ @instance_index = 0
31
+ @use_extension = false
32
+ @interact = false
33
+ @remote_address = nil
34
+ @api_container = nil
35
+ @version = nil
36
+ @secure_docker_api = true
37
+ @build_args = nil
38
+ end
39
+
40
+ def find_machine_apis
41
+ @run_on_apis = []
42
+ @all_docker_apis_in_cluster = []
43
+ if (@deploy_config != nil) && (@deploy_config[:hosts] != nil)
44
+ @deploy_config[:hosts].each do |host_key|
45
+ host = Hosts::HOSTS[host_key]
46
+ address = "tcp://#{host[:address]}:#{host[:docker_port]}"
47
+ docker_api = DockerApi.new(address, host[:ssl])
48
+ @all_docker_apis_in_cluster << docker_api
49
+ @run_on_apis << docker_api if (address == @remote_address) || ( @deploy_config[:targets][run_name] == :all)
50
+ end
51
+ else
52
+ # Config not given, we need to create api from remote address and ssl setting. This is the only api for this class.
53
+ @docker_host = @docker_host || ENV['DOCKER_HOST']
54
+ @docker_host_secure = @docker_host_secure || (ENV['DOCKER_TLS_VERIFY'].to_s == '1')
55
+ docker_api = DockerApi.new(@docker_host, @docker_host_secure)
56
+
57
+ @all_docker_apis_in_cluster << docker_api
58
+ @run_on_apis << docker_api
59
+ end
60
+ end
61
+
62
+ def run_name
63
+ return @run_config['name']
64
+ end
65
+
66
+ def image_version
67
+ return @version if (@version != nil)
68
+ the_version = 'latest'
69
+ the_version = @build_config['version'] if (@build_config) && (@build_config['version'] != nil)
70
+ return the_version
71
+ end
72
+
73
+ def kill_all(name_prefix)
74
+ find_machine_apis
75
+ @all_docker_apis_in_cluster.each do |api|
76
+ #options = {filters: {status: [:running, :exited, :paused, :restarting]}}
77
+ options = {}
78
+ containers = Docker::Container.all(options.to_json, api.connection)
79
+ containers.each do |container|
80
+ if (container.json["Name"].start_with?("/#{name_prefix}"))
81
+ pp "Killing and removing: #{container.json["Name"]}"
82
+ container.kill() if (container.json["State"]["Running"])
83
+ container.remove() if (container != nil)
84
+ else
85
+ pp " * Container didn't match: #{container.json["Name"]}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+
92
+ def run
93
+ find_machine_apis
94
+ raise "No run on apis" if (@run_on_apis.count == 0)
95
+
96
+ code = @code
97
+ instance_index = 1
98
+ if (@instance_index == :next)
99
+ options = {filters: {status: [:running]}}
100
+ @active_container_names = []
101
+ containers = Docker::Container.all(options.to_json, @run_on_apis.first.connection)
102
+ containers.each do |container|
103
+ container_json = container.json
104
+ container_name = container_json["Name"]
105
+ @active_container_names << container_name
106
+ end
107
+
108
+ instance_index = 1
109
+ container_name_taken = true
110
+ while (true)
111
+ instance_ext = "_#{instance_index}"
112
+ instance_name = "/#{run_name}#{instance_ext}"
113
+ if (!@active_container_names.include? instance_name)
114
+ @instance_index = instance_index
115
+ break
116
+ else
117
+ instance_index += 1
118
+ end
119
+ end
120
+ end
121
+
122
+ instance_ext = ""
123
+ instance_ext = "_#{@instance_index}" if (@use_extension)
124
+ instance_name = "#{run_name}#{instance_ext}"
125
+
126
+ @all_docker_apis_in_cluster.each do |docker_api|
127
+ kill_previous_containers_in_cluster(docker_api, instance_name)
128
+ end
129
+ image_name = image_name_with_registry()
130
+
131
+ @run_on_apis.each do |docker_api|
132
+ if (!@run_untagged)
133
+ pull_image_to_host(docker_api, registry)
134
+ end
135
+ run_container(docker_api, instance_name)
136
+ end
137
+ end
138
+
139
+
140
+ private
141
+
142
+ def pull_image_to_host(docker_api, registry)
143
+ return if (registry == nil) || (@run_untagged)
144
+ Excon.defaults[:write_timeout] = 500
145
+ Excon.defaults[:read_timeout] = 500
146
+ options = {}
147
+ options[:repo] = registry
148
+ options[:fromImage] = "#{image_name_with_registry}:#{image_version}"
149
+ pp "See if Host needs to pull image (#{options[:fromImage]}). Host: #{docker_api.connection}"
150
+
151
+ image = Docker::Image.create(options, nil, docker_api.connection)
152
+ end
153
+
154
+ def run_container(docker_api, instance_name)
155
+ create_options = @run_config['options'] || {}
156
+
157
+ if (!image_name_with_registry.include? ":")
158
+ create_options['Image'] = "#{image_name_with_registry}:#{image_version}"
159
+ else
160
+ create_options['Image'] = image_name_with_registry
161
+ end
162
+ create_options['name'] = instance_name
163
+ create_options['Labels'] = { deployer: @user, deploy_id: @deployment_id }
164
+ create_options['Tty'] = true if (@interact == true)
165
+ create_options['Entrypoint'] = ["/bin/bash"] if (@interact == true)
166
+
167
+ pp "Running container: #{instance_name}\nOptions: #{create_options}"
168
+ cli_command = "docker run --name #{instance_name}"
169
+ if (create_options['Env'] != nil)
170
+ create_options['Env'].each do |env|
171
+ cli_command += " -e #{env}"
172
+ end
173
+ end
174
+ if (create_options['HostConfig'] != nil)
175
+ host_config = create_options['HostConfig']
176
+ binds = host_config['Binds']
177
+ if (binds != nil)
178
+ binds.each do |bind|
179
+ cli_command += " -v #{bind}"
180
+ end
181
+ end
182
+ links = host_config['Links']
183
+ if (links != nil)
184
+ links.each do |link|
185
+ cli_command += " --link #{link}"
186
+ end
187
+ end
188
+ ports = host_config['PortBindings']
189
+ ports.keys.each do |port|
190
+ container_port = port.split("/").first
191
+ host_port = container_port
192
+ port_binds = ports[port]
193
+ port_binds.each do |bind|
194
+ host_port = bind['HostPort'] if (bind['HostPort'] != nil)
195
+ cli_command += " -p #{host_port}:#{container_port}"
196
+ end
197
+ if (port_binds.length == 0)
198
+ cli_command += " -p #{host_port}:#{container_port}"
199
+ end
200
+ end
201
+ end
202
+ cli_command += " -d"
203
+ cli_command += " #{create_options['Image']}"
204
+ cli_command += " #{create_options['Entrypoint']}" if (create_options['Entrypoint'] != nil)
205
+ puts "CLI Command: #{cli_command}"
206
+
207
+ @api_container = Docker::Container.create(create_options, docker_api.connection)
208
+ puts @api_container.start
209
+ if (@interact)
210
+ pp "=================================================================="
211
+ pp "CONTAINER STARTED FOR INTERACTION - ENTRYPOINT NOT CALLED"
212
+ pp "To attach: docker exec -it '#{instance_name}' bash"
213
+ pp "=================================================================="
214
+ end
215
+ end
216
+
217
+
218
+ def kill_previous_containers_in_cluster(docker_api, instance_name)
219
+ pp "Killing and removing containers with same name: #{instance_name}"
220
+ @api_container = nil
221
+ begin
222
+ @api_container = Docker::Container.get(instance_name, {}, docker_api.connection)
223
+ puts "Killing and removing container: #{instance_name} in #{docker_api.connection}"
224
+ @api_container.kill()
225
+ rescue Docker::Error::NotFoundError => not_found_error
226
+ puts "No container with name \"#{instance_name}\" running. No need to kill."
227
+ end
228
+
229
+ if (@api_container)
230
+ begin
231
+ pp "Removing container: #{instance_name}"
232
+ @api_container.remove() if (@api_container != nil)
233
+ rescue Docker::Error::NotFoundError => remove_error
234
+ puts "Didn't find image to remove: #{instance_name}"
235
+ end
236
+ end
237
+ end
238
+
239
+
240
+ def image_name_with_registry
241
+ image_name = @image_name
242
+ if (registry != nil) && (!@run_untagged)
243
+ registry_without_protocol = registry.split("://").last
244
+ image_name = "#{registry_without_protocol}/#{@image_name}"
245
+ end
246
+ return image_name
247
+ end
248
+
249
+ def container_state
250
+ return @api_container.json['State']
251
+ end
252
+
253
+ def wait_for_completion
254
+ while (container_state()["Running"] == true)
255
+ sleep 1
256
+ puts "Waiting for container #{run_name} to finish."
257
+ end
258
+ exit_code = container_state()['ExitCode']
259
+ raise "Container failed (#{image_name_with_registry()}) Exit code: #{exit_code}" if (exit_code != 0)
260
+ end
261
+
262
+
263
+ end
@@ -0,0 +1,49 @@
1
+ FROM ruby:2.1.5
2
+
3
+
4
+ ENV GIT_REPO_ADDRESS <<GIT_REPO_ADDRESS>>
5
+ ENV DOCKER_IMAGE true
6
+ ENV APP_HOME /application
7
+ ENV RAILS_ENV production
8
+
9
+
10
+ RUN apt-get update && apt-get install -y --force-yes \
11
+ autoconf \
12
+ build-essential \
13
+ cmake \
14
+ pkg-config \
15
+ libssl-dev \
16
+ libyaml-dev \
17
+ libreadline6-dev \
18
+ zlib1g-dev \
19
+ libffi-dev \
20
+ libncurses5-dev \
21
+ libgdbm3 \
22
+ libgdbm-dev \
23
+ libsqlite3-dev \
24
+ libmysqlclient-dev \
25
+ libv8-dev \
26
+ # telnet \
27
+ mysql-client \
28
+ git \
29
+ netcat \
30
+ wget && \
31
+ gem install --no-document bundler && \
32
+ echo "Set up ssh credentials for gitlab, so we can clone olex at docker image run time." && \
33
+ mkdir /root/.ssh
34
+
35
+
36
+ # COPY files/rails_application.tar.gz /application
37
+ COPY files/ssh/* /root/.ssh/
38
+
39
+ RUN echo "RUNNING CONTAINER ENTRYPOINT" && \
40
+ rm -rf "$APP_HOME" && \
41
+ git clone "$GIT_REPO_ADDRESS" "$APP_HOME" &&\
42
+ cd "$APP_HOME" && \
43
+ bundle install --without test development
44
+
45
+ EXPOSE 3000
46
+
47
+ # COPY entrypoint.sh /entrypoint.sh
48
+ # ENTRYPOINT ["/entrypoint.sh"]
49
+ # CMD ["/application/rails","s"]
@@ -0,0 +1,46 @@
1
+ FROM ruby:2.1.5
2
+
3
+ ENV SQL_HOST <<SQL_HOST>>
4
+ ENV SQL_HOST_PORT 3306
5
+
6
+ ENV DOCKER_IMAGE true
7
+ ENV APP_HOME /application
8
+ ENV RAILS_ENV production
9
+
10
+
11
+ RUN apt-get update && apt-get install -y --force-yes \
12
+ autoconf \
13
+ build-essential \
14
+ cmake \
15
+ pkg-config \
16
+ libssl-dev \
17
+ libyaml-dev \
18
+ libreadline6-dev \
19
+ zlib1g-dev \
20
+ libffi-dev \
21
+ libncurses5-dev \
22
+ libgdbm3 \
23
+ libgdbm-dev \
24
+ libsqlite3-dev \
25
+ libmysqlclient-dev \
26
+ libv8-dev \
27
+ mysql-client \
28
+ netcat \
29
+ wget && \
30
+ gem install --no-document bundler &&\
31
+ mkdir -p "$APP_HOME" &&\
32
+ gem install puma
33
+
34
+ COPY files/rails_application.tar.gz "$APP_HOME"
35
+
36
+ RUN echo "PREPARING APPLICATION" && \
37
+ cd "$APP_HOME" &&\
38
+ tar xzf rails_application.tar.gz && \
39
+ bundle install --without test development
40
+
41
+ COPY files/database.yml "$APP_HOME/config"
42
+
43
+ EXPOSE 3000
44
+ COPY wait_for_port.sh /wait_for_port.sh
45
+ COPY entrypoint.sh /entrypoint.sh
46
+ ENTRYPOINT ["/entrypoint.sh"]
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ cd "$APP_HOME"
3
+
4
+ echo "================ Waiting for MySQL's port to respond"
5
+
6
+ /bin/bash /wait_for_port.sh "$SQL_HOST" "$SQL_HOST_PORT"
7
+
8
+ echo "================ Migrating Openlogic database"
9
+ rake db:create db:migrate
10
+
11
+ # echo "================ Starting memcache - oh no, two long running processes in the same container."
12
+ # memcached -d -uroot
13
+
14
+
15
+ rails s -d Puma -b 0.0.0.0 -p 3000
16
+ tail -100f ./log/production.log
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ file=$1
4
+
5
+ while ! exit | [ -e "$file" ];
6
+ do echo "Waiting for file: $file" && sleep 3;
7
+ done
8
+ echo ""
9
+ echo "File found: $file"
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ host=$1
4
+ port=$2
5
+
6
+ echo "Waiting for port to respond: $host:$port"
7
+ while ! exit | nc -q 1 "$host" "$port";
8
+ do echo "Waiting for: $host Port: $port" && sleep 3;
9
+ done
10
+ echo ""
11
+ echo "Port responding: $host:$port"
@@ -0,0 +1,131 @@
1
+ require 'fileutils'
2
+
3
+ class InitializeProject
4
+ def app_docker_path
5
+ project_dir = FileUtils.pwd
6
+ docker_dir = "#{project_dir}/docker"
7
+ FileUtils.mkdir_p("#{docker_dir}/files")
8
+ return docker_dir
9
+ end
10
+
11
+ def fill_in_file_param(file_path, param_name, param_value)
12
+ new_content = nil
13
+ File.open(file_path, "r") do |file|
14
+ content = file.read
15
+ search_str = "<<#{param_name}>>"
16
+ index = content.index(search_str)
17
+ content_start = content[0..(index - 1)]
18
+ content_end = content[(index + search_str.length), content.length]
19
+ new_content = content_start + param_value + content_end
20
+ end
21
+ File.open(file_path, "w") do |file|
22
+ file.write(new_content)
23
+ end
24
+ end
25
+
26
+ def create_configuration
27
+ #Use directory name as default for image name.
28
+ default_image_name = FileUtils.pwd.split('/').last
29
+ default_image_name = default_image_name.split('_').join("-")
30
+ default_image_name = default_image_name.split(' ').join("-")
31
+
32
+ puts "Creating configurations."
33
+ puts "Enter image name of rails app (#{default_image_name}):"
34
+ image_name = STDIN.gets.chomp
35
+ if (image_name.length == 0)
36
+ image_name = default_image_name
37
+ end
38
+
39
+ db_run_time_name = image_name + "-db"
40
+
41
+ docker_dir = app_docker_path()
42
+ FileUtils.mkdir_p("#{docker_dir}/files")
43
+
44
+ this_dir = File.dirname(__FILE__)
45
+ docker_file_path = "#{docker_dir}/Dockerfile"
46
+ copy_docker_file = true
47
+ if (File.exist?(docker_file_path))
48
+ puts "Dockerfile already exists in #{docker_dir}. Overwrite? (y/n)"
49
+ overwrite = STDIN.gets.chomp
50
+ if (overwrite.downcase == "n")
51
+ copy_docker_file = false
52
+ end
53
+ end
54
+
55
+ if (copy_docker_file)
56
+ FileUtils.cp("#{this_dir}/image_files/DockerfileSelf", docker_file_path)
57
+ fill_in_file_param(docker_file_path, 'SQL_HOST', db_run_time_name)
58
+ puts "Copied standard Dockerfile to: #{docker_dir}"
59
+ end
60
+
61
+
62
+ copy_entry_file = true
63
+ entry_file_path = "#{docker_dir}/entrypoint.sh"
64
+ if (File.exist?(entry_file_path))
65
+ puts "Entrypoint.sh already exists in #{docker_dir}. Overwrite? (y/n)"
66
+ overwrite = STDIN.gets.chomp
67
+ if (overwrite.downcase == "n")
68
+ copy_entry_file = false
69
+ end
70
+ end
71
+
72
+ if (copy_entry_file)
73
+ FileUtils.cp("#{this_dir}/image_files/entrypoint.sh", "#{docker_dir}/entrypoint.sh")
74
+ puts "Copied entry point file to: #{docker_dir}"
75
+ end
76
+
77
+ FileUtils.cp("#{this_dir}/image_files/wait_for_port.sh", "#{docker_dir}/wait_for_port.sh")
78
+
79
+
80
+ main_configuration = {
81
+ registry: "https://my.registry.com:5000",
82
+ build: {
83
+ name: image_name
84
+ },
85
+ run: {
86
+ name: image_name,
87
+ options: {
88
+ "Env" => [
89
+ "IS_DOCKER=true"
90
+ ],
91
+ "HostConfig" => {
92
+ "Links" => ["#{db_run_time_name}:#{db_run_time_name}"],
93
+ "PortBindings" => {
94
+ "3000/tcp" => [
95
+ {"HostIp" => "", "HostPort" => "3000"}
96
+ ]
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ sql_configuration = {
104
+ run: {
105
+ name: db_run_time_name,
106
+ image_name: "percona:5.6",
107
+ options: {
108
+ "Env" => [
109
+ "MYSQL_ROOT_PASSWORD=password"
110
+ ],
111
+ "HostConfig" => {
112
+ "PortBindings" => {
113
+ "3306/tcp" => []
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+
121
+ configs = [{sql: sql_configuration}, {rails_app: main_configuration}]
122
+
123
+ output = JSON.pretty_generate(configs)
124
+ config_file_path = "#{app_docker_path}/docker-rails.json"
125
+ File.open(config_file_path, "w") do |f|
126
+ f.write(output)
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,62 @@
1
+ module LaunchSupport
2
+
3
+ def is_port_open?(ip, port)
4
+ begin
5
+ Timeout::timeout(1) do
6
+ begin
7
+ puts "Connecting to #{ip}: #{port}"
8
+ s = TCPSocket.new(ip, port)
9
+ s.close
10
+ return true
11
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
12
+ return false
13
+ end
14
+ end
15
+ rescue Timeout::Error
16
+ end
17
+ return false
18
+ end
19
+
20
+
21
+ def wait_for_short_lived_image_to_complete(image_run_name)
22
+ done = false
23
+ while (!done)
24
+ #puts "Inspecting image: #{image_run_name}"
25
+ output = `docker inspect #{image_run_name}`
26
+ #puts "Output: #{output}"
27
+ json = JSON.parse(output)
28
+ state = json.first['State']
29
+ is_running = state['Running']
30
+ exit_code = state['ExitCode']
31
+ puts "Waiting for short-lived container to complete: \"#{image_run_name}\""
32
+ if (exit_code == 0) && (is_running == false)
33
+ done = true
34
+ elsif (exit_code != 0)
35
+ raise "Container failed: #{image_run_name} - Run: docker logs -f #{image_run_name}"
36
+ else
37
+ sleep 3
38
+ end
39
+ end
40
+ end
41
+
42
+ def wait_for_port(ip, port)
43
+ while (!is_port_open?(ip, port))
44
+ puts "Port not ready: #{ip}:#{port}"
45
+ sleep 3
46
+ end
47
+ end
48
+
49
+
50
+ def host
51
+ output = `boot2docker ip`
52
+ if (output.strip.length == 0) || (output.include?("command not found")) || (output.include?("is not running"))
53
+ docker_machine_active = `docker-machine active`
54
+ docker_machine_active = docker_machine_active[0..(docker_machine_active.length - 2)]
55
+ output = `docker-machine url #{docker_machine_active}`
56
+ address = output.split("//")[1].split(":").first
57
+ return address
58
+ else
59
+ return output.strip
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "../support/docker_rails_config_reader"
2
+ require_relative "../support/docker_rails_app_creator"
3
+
4
+
5
+ namespace :docker do
6
+
7
+
8
+ desc "Build docker image of this project"
9
+ task :build => :environment do
10
+ config_reader = DockerRailsConfigReader.new
11
+ config_reader.definitions.each do |definition|
12
+ definition_name = definition.keys.first
13
+ definition_value = definition[definition_name]
14
+ if (definition_value['build'])
15
+ builder = DockerRailsAppCreator.new(definition_value)
16
+ builder.docker_host = "tcp://127.0.0.1:2375"
17
+ builder.docker_host_secure = true
18
+ builder.build_it
19
+ end
20
+ end
21
+ end
22
+
23
+ desc "Push docker image to repo"
24
+ task :push => :environment do
25
+ config_reader = DockerRailsConfigReader.new
26
+ config_reader.definitions.each do |definition|
27
+ definition_name = definition.keys.first
28
+ definition_value = definition[definition_name]
29
+ if (definition_value['build'])
30
+ builder = DockerRailsAppCreator.new(definition_value)
31
+ builder.docker_host = "tcp://127.0.0.1:2375"
32
+ builder.docker_host_secure = true
33
+ registry = definition_value['registry']
34
+ if (registry)
35
+ builder.tag_it(registry)
36
+ builder.push_it(registry)
37
+ else
38
+ puts "No registry definited. Ignoring push command."
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "../support/docker_rails_app_creator"
2
+ require_relative "../support/initialize_project"
3
+
4
+
5
+ namespace :docker do
6
+
7
+ desc "Build docker image of this project"
8
+ task :install => :environment do
9
+ initializer = InitializeProject.new
10
+ initializer.create_configuration()
11
+ end
12
+
13
+ end
@@ -0,0 +1,38 @@
1
+ require_relative "../support/docker_rails_app_creator"
2
+
3
+ namespace :docker do
4
+
5
+ desc "Run Docker image"
6
+ task :run => :environment do
7
+ config_reader = DockerRailsConfigReader.new
8
+ config_reader.definitions.each do |definition|
9
+ definition_name = definition.keys.first
10
+ definition_value = definition[definition_name]
11
+ if (definition_value['run'])
12
+ docker_runner = DockerRunner.new(definition_value)
13
+ docker_runner.docker_host = "tcp://127.0.0.1:2375"
14
+ docker_runner.docker_host_secure = true
15
+ docker_runner.run
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+ desc "Run Local Docker image"
22
+ task :run_local => :environment do
23
+ config_reader = DockerRailsConfigReader.new
24
+ config_reader.definitions.each do |definition|
25
+ definition_name = definition.keys.first
26
+ definition_value = definition[definition_name]
27
+ if (definition_value['run'])
28
+ docker_runner = DockerRunner.new(definition_value)
29
+ docker_runner.docker_host = "tcp://127.0.0.1:2375"
30
+ docker_runner.docker_host_secure = true
31
+ docker_runner.run_untagged = true
32
+ docker_runner.run
33
+ end
34
+ end
35
+ end
36
+
37
+
38
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: docker-rails-app
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.6
5
+ platform: ruby
6
+ authors:
7
+ - Mike Moore
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-01-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: docker-api
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.31'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.31'
55
+ description: Puts your rails app into a Docker image
56
+ email:
57
+ - m.moore.denver@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - bin/console
63
+ - bin/setup
64
+ - lib/docker_rails_app.rb
65
+ - lib/docker_rails_app/import_gem_rake_tasks.rb
66
+ - lib/docker_rails_app/version.rb
67
+ - lib/support/docker_api.rb
68
+ - lib/support/docker_image_creator.rb
69
+ - lib/support/docker_rails_app_creator.rb
70
+ - lib/support/docker_rails_config_reader.rb
71
+ - lib/support/docker_registry.rb
72
+ - lib/support/docker_runner.rb
73
+ - lib/support/image_files/DockerfileRepo
74
+ - lib/support/image_files/DockerfileSelf
75
+ - lib/support/image_files/entrypoint.sh
76
+ - lib/support/image_files/wait_for_file.sh
77
+ - lib/support/image_files/wait_for_port.sh
78
+ - lib/support/initialize_project.rb
79
+ - lib/support/launch_support.rb
80
+ - lib/tasks/deploy.rake
81
+ - lib/tasks/initialize.rake
82
+ - lib/tasks/run.rake
83
+ homepage:
84
+ licenses:
85
+ - MIT
86
+ metadata: {}
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ - lib/docker_rails_app
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.4.5.1
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Puts your rails app into a Docker image
108
+ test_files: []