docker-rails-app 0.3.6

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 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: []