docker_brick 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.project +17 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE.txt +24 -0
  6. data/README.md +29 -0
  7. data/Rakefile +2 -0
  8. data/bin/brick +11 -0
  9. data/docker_brick.gemspec +29 -0
  10. data/lib/brick/application.rb +35 -0
  11. data/lib/brick/cli/build.rb +28 -0
  12. data/lib/brick/cli/core/subcommand_loader.rb +39 -0
  13. data/lib/brick/cli/help.rb +16 -0
  14. data/lib/brick/cli/project_new.rb +29 -0
  15. data/lib/brick/cli/run.rb +113 -0
  16. data/lib/brick/cli/service_new.rb +44 -0
  17. data/lib/brick/cli/up.rb +46 -0
  18. data/lib/brick/cli.rb +230 -0
  19. data/lib/brick/cli__validator.rb +28 -0
  20. data/lib/brick/config.rb +9 -0
  21. data/lib/brick/docker/docker_client.rb +160 -0
  22. data/lib/brick/generators/new_project_generator/templates/fig.yml +0 -0
  23. data/lib/brick/generators/new_project_generator.rb +51 -0
  24. data/lib/brick/generators/new_service_generator.rb +24 -0
  25. data/lib/brick/generators.rb +10 -0
  26. data/lib/brick/mixin/aliasing.rb +24 -0
  27. data/lib/brick/mixin/colors.rb +24 -0
  28. data/lib/brick/mixin/convert_to_class_name.rb +52 -0
  29. data/lib/brick/mixin/docker_support.rb +253 -0
  30. data/lib/brick/mixin/yaml_helper.rb +18 -0
  31. data/lib/brick/mixin.rb +8 -0
  32. data/lib/brick/models/project.rb +139 -0
  33. data/lib/brick/models/service.rb +276 -0
  34. data/lib/brick/models.rb +13 -0
  35. data/lib/brick/monkey_patches/cli.rb +24 -0
  36. data/lib/brick/monkey_patches/connection.rb +15 -0
  37. data/lib/brick/monkey_patches/docker_container.rb +28 -0
  38. data/lib/brick/monkey_patches/hash.rb +16 -0
  39. data/lib/brick/monkey_patches.rb +5 -0
  40. data/lib/brick/version.rb +4 -0
  41. data/lib/brick.rb +34 -0
  42. data/spec/brick_update.sh +7 -0
  43. data/spec/integration/brick/models/project_spec.rb +70 -0
  44. data/spec/projects/nc_test/fig.yml +14 -0
  45. data/spec/projects/nc_test/ncserver/Dockerfile +7 -0
  46. data/spec/projects/rails/myapp/Dockerfile +7 -0
  47. data/spec/projects/rails/myapp/Gemfile +2 -0
  48. data/spec/projects/rails/myapp/fig.yml +13 -0
  49. data/spec/shell/brick_update.sh +7 -0
  50. data/spec/spec_helper.rb +19 -0
  51. data/spec/unit/brick/mixin/docker_support.yml +33 -0
  52. data/spec/unit/brick/mixin/docker_support_spec.rb +66 -0
  53. data/spec/unit/brick/models/dockerfile/Dockerfile +7 -0
  54. data/spec/unit/brick/models/fig_build.yml +3 -0
  55. data/spec/unit/brick/models/fig_completed.yml +41 -0
  56. data/spec/unit/brick/models/fig_dependency.yml +19 -0
  57. data/spec/unit/brick/models/fig_single.yml +6 -0
  58. data/spec/unit/brick/models/fig_volumes.yml +10 -0
  59. data/spec/unit/brick/models/fig_volumes_from.yml +19 -0
  60. data/spec/unit/brick/models/project_spec.rb +70 -0
  61. data/spec/unit/brick/models/service_spec.rb +80 -0
  62. metadata +231 -0
@@ -0,0 +1,253 @@
1
+ require 'shellwords'
2
+ require 'pathname'
3
+
4
+ module Brick::Mixin
5
+ module DockerSupport
6
+
7
+ #from yml file to the configuration for creating container
8
+ def create_config hash
9
+ hash=transform_docker_hash hash
10
+
11
+ create_config_for_port hash
12
+
13
+ create_config_for_volumes hash
14
+
15
+ hash
16
+ end
17
+
18
+ def start_config hash
19
+ hash=transform_docker_hash hash
20
+
21
+ start_config_for_port hash
22
+
23
+ start_config_for_volumes hash
24
+
25
+ hash
26
+ end
27
+
28
+ #the format is captalized
29
+ private
30
+ def transform_docker_hash hash
31
+ hash= Hash[hash.map do |k,v|
32
+ keys=k.split('_')
33
+
34
+ keys.map!{|key|key.capitalize}
35
+
36
+ [keys.join(''), v]
37
+ end
38
+ ]
39
+
40
+ common_config_for_cmd hash
41
+
42
+ common_config_for_env hash
43
+
44
+ common_config_for_volumes hash
45
+
46
+ hash
47
+ end
48
+
49
+
50
+
51
+ def common_config_for_cmd hash
52
+ cmd= hash.delete('Command')
53
+
54
+ #hash['Cmd']=cmd.split(' ') unless cmd.nil?
55
+
56
+ unless cmd.nil?
57
+ if cmd.instance_of? Array
58
+ hash['Cmd'] = cmd
59
+ else
60
+ hash['Cmd'] = Shellwords.split(cmd)
61
+ end
62
+ end
63
+
64
+ hash
65
+ end
66
+
67
+ #common configuration for environment variable
68
+ def common_config_for_env hash
69
+ #Support environment variables
70
+ env_variables = hash.delete('Environment')
71
+
72
+ unless env_variables.nil?
73
+ if env_variables.instance_of? Array
74
+ hash['Env'] = env_variables
75
+ elsif env_variables.instance_of? Hash
76
+ var_arrays = []
77
+ env_variables.each {|key, value| var_arrays<<"#{key}=#{value}" }
78
+ hash['Env'] = var_arrays
79
+ end
80
+ end
81
+ hash
82
+ end
83
+
84
+ def common_config_for_volumes hash
85
+ #volumes
86
+ unless hash["Volumes"].nil?
87
+ volumes = hash["Volumes"]
88
+
89
+ if volumes.instance_of? Array
90
+ volumes.map!{|vo|
91
+ vo_parts = vo.split(":")
92
+
93
+ container_volume = nil
94
+
95
+ host_volume = vo_parts[0]
96
+
97
+ option = "rw"
98
+
99
+ if(Pathname.new(host_volume).relative?)
100
+ host_volume = File.absolute_path(File.join(::Brick::Config[:project_dir],host_volume))
101
+ end
102
+
103
+
104
+ if vo_parts.size==1
105
+ container_volume = host_volume
106
+ elsif vo_parts.size>=2
107
+ container_volume = vo_parts[1]
108
+
109
+ if(Pathname.new(container_volume).relative?)
110
+ container_volume = File.absolute_path(File.join(::Brick::Config[:project_dir],container_volume))
111
+ end
112
+ end
113
+
114
+ if vo_parts.size==3
115
+ option = vo_parts[2]
116
+ end
117
+
118
+
119
+ [host_volume, container_volume, option].join(':')
120
+ }
121
+ else
122
+ raise "the value of volumes should be an array"
123
+ end
124
+ end
125
+
126
+ hash
127
+ end
128
+
129
+ def create_config_for_volumes hash
130
+ #create config for volumes
131
+ unless hash["Volumes"].nil?
132
+ volumes = hash.delete('Volumes')
133
+
134
+ volume_hash={}
135
+
136
+ volumes.each{|vo|
137
+ vo_parts = vo.split(':')
138
+
139
+ volume_hash[vo_parts[1]] = {}
140
+
141
+ }
142
+
143
+ hash['Volumes'] = volume_hash
144
+
145
+ hash
146
+ end
147
+ hash
148
+ end
149
+
150
+ def start_config_for_volumes hash
151
+ #start config for volumes
152
+ #start config for volumes
153
+ unless hash["Volumes"].nil?
154
+ binds = hash.delete('Volumes')
155
+
156
+ hash["Binds"] = binds
157
+ end
158
+ hash
159
+ end
160
+
161
+ #the port configuration for creating container
162
+ def create_config_for_port hash
163
+ exposed_ports = []
164
+
165
+
166
+ #add expose ports
167
+ unless hash["Ports"].nil?
168
+ ports = hash.delete "Ports"
169
+
170
+ ports.each{|port|
171
+
172
+ container_port = (port.split(':'))[-1]
173
+
174
+ exposed_ports << container_port
175
+ }
176
+
177
+ end
178
+
179
+ #Add expose to exposed ports
180
+ unless hash["Expose"].nil?
181
+ exposes = hash.delete "Expose"
182
+
183
+ exposes.each{|expose|
184
+ exposed_ports << expose
185
+ }
186
+ end
187
+
188
+
189
+
190
+ if exposed_ports.size > 0
191
+
192
+ proto = 'tcp'
193
+
194
+ exposed_port_hash = Hash.new
195
+
196
+ exposed_ports.each {|container_port| exposed_port_hash["#{container_port}/#{proto}"]={}}
197
+
198
+ hash["ExposedPorts"]=exposed_port_hash
199
+ end
200
+
201
+ hash
202
+ end
203
+
204
+ #the port configuration for starting container
205
+ def start_config_for_port hash
206
+ #the setting for start config
207
+ port_bindings = {}
208
+
209
+ unless hash["Ports"].nil?
210
+ ports = hash.delete "Ports"
211
+
212
+ ports.each{|port|
213
+
214
+ port_definition = port.split(':')
215
+
216
+ proto ="tcp"
217
+
218
+ if port_definition.size > 1
219
+
220
+ container_port = port_definition[-1]
221
+
222
+ host_port = port_definition[-2]
223
+
224
+ port_bindings["#{container_port}/#{proto}"] = [{"HostPort"=>host_port}]
225
+
226
+ # port_bindings << {"#{container_port}/#{proto}"=>[{"HostPort"=>host_port}]}
227
+ else
228
+ port_bindings["#{port}/#{proto}"] = [{"HostPort"=>port}]
229
+ end
230
+ }
231
+
232
+ hash["PortBindings"]=port_bindings
233
+ end
234
+ hash
235
+ end
236
+
237
+ def determine_dockerfile_path dockerfile_path, project_dir
238
+
239
+ pathname= Pathname.new(dockerfile_path)
240
+
241
+ if pathname.absolute?
242
+ real_dockerfile_path = dockerfile_path
243
+ else
244
+ unless project_dir.nil?
245
+ real_dockerfile_path = File.absolute_path(File.join(project_dir, dockerfile_path))
246
+ end
247
+ end
248
+
249
+ real_dockerfile_path
250
+ end
251
+
252
+ end
253
+ end
@@ -0,0 +1,18 @@
1
+ require 'yaml'
2
+
3
+ module Brick::Mixin
4
+ module YamlHelper
5
+ def load_yaml_file(path, expected_type = Hash)
6
+ raise("Cannot find file `#{path}'") unless File.exists?(path)
7
+ yaml = YAML::load_file(path)
8
+
9
+ if expected_type && !yaml.is_a?(expected_type)
10
+ raise "Incorrect file format in `#{path}', #{expected_type} expected"
11
+ end
12
+
13
+ yaml
14
+ rescue SystemCallError => e
15
+ raise "Cannot load YAML file at `#{path}': #{e}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+
2
+ require "brick/mixin/convert_to_class_name"
3
+
4
+ require "brick/mixin/aliasing"
5
+
6
+ require 'brick/mixin/docker_support'
7
+
8
+ require 'brick/mixin/colors'
@@ -0,0 +1,139 @@
1
+ require 'brick/mixin/yaml_helper'
2
+ require 'brick/docker/docker_client'
3
+ #require 'deepstruct'
4
+
5
+ module Brick
6
+ module Models
7
+ class Project
8
+
9
+ include Brick::Mixin::YamlHelper
10
+
11
+ attr_accessor :services, :docker_client, :name, :recreate, :insecure_registry
12
+
13
+ def initialize(project_name ,options={},client=nil)
14
+
15
+ @name = project_name
16
+
17
+ unless options[:config_file].nil?
18
+ @config_file = options[:config_file]
19
+ end
20
+
21
+ @docker_client = client ? client : Brick::Docker::DockerClient::default
22
+
23
+ init_services_from_config(@config_file)
24
+
25
+ end
26
+
27
+
28
+ def get_services(service_name, inlcude_links=false)
29
+
30
+ end
31
+
32
+ def get_service(name)
33
+
34
+ service = nil
35
+
36
+ unless @services.nil?
37
+
38
+ service = @services[name]
39
+
40
+ end
41
+
42
+ service
43
+ end
44
+
45
+ #initialize the configuration of each service, and put it into services
46
+ def init_services_from_config(config_file=nil)
47
+ config_hash = load_yaml_file config_file
48
+
49
+ # config = config_hash.to_ostruct
50
+
51
+ @services = {}
52
+
53
+ config_hash.each_key{|key|
54
+ # @services << Service.new(key,eval("config.#{key}"))
55
+ @services[key] =Service.new("#{@name}_#{key}_1",config_hash[key],@docker_client)
56
+ }
57
+
58
+ @services.each_key{|key|
59
+ service = @services[key]
60
+ service.update_links @services
61
+ service.update_volumes_from @services
62
+ service.update_image_for_building_tag("#{self.name}_#{key}")
63
+ }
64
+
65
+ end
66
+
67
+
68
+ def up(detach_mode = true, enable_link=true, recreate=false)
69
+
70
+ self.services.each_key{|key| service= services[key]
71
+
72
+ if service.container_exist?
73
+ puts "Recreating #{service.name} ..."
74
+ else
75
+ puts "Creating #{service.name} ..."
76
+ end
77
+
78
+ if service.can_be_built?
79
+ unless service.image_exist?
80
+ # by default, not set cache
81
+ service.build nil, true, ::Brick::Config[:project_dir]
82
+ end
83
+ end
84
+
85
+ service.run enable_link, recreate, detach_mode
86
+
87
+
88
+ }
89
+
90
+ Service.wait_for_deamon
91
+ end
92
+
93
+
94
+ #create the service according to the service name
95
+ def run_services(service_name, enable_link=true)
96
+
97
+ service = @services[service_name]
98
+
99
+ to_build = false
100
+
101
+ if service.can_be_built?
102
+
103
+ if service.image_exist?
104
+ if ::Brick::Config[:rebuild]
105
+ to_build=true
106
+ else
107
+ puts "The image #{service.image_name} already exists!"
108
+ end
109
+ else
110
+ to_build = true
111
+ end
112
+
113
+ end
114
+
115
+ if to_build
116
+ service.build nil, true, ::Brick::Config[:project_dir]
117
+ end
118
+
119
+ raise ServicesNotFoundException.new("service #{service_name} is not found in #{@config_file}") if service.nil?
120
+ service.run enable_link
121
+
122
+ end
123
+
124
+ def build_services no_cache=false, project_dir=nil
125
+ self.services.each_key{|key| service= services[key]
126
+
127
+
128
+ if service.can_be_built?
129
+ service.build("#{self.name}_#{key}",no_cache,project_dir)
130
+ else
131
+ ::Brick::CLI::logger.info("uses an image, skipping #{key}")
132
+ end
133
+
134
+ }
135
+ end
136
+
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,276 @@
1
+ require 'brick/mixin'
2
+ require 'colorize'
3
+
4
+
5
+ module Brick
6
+ module Models
7
+ class Service
8
+
9
+ include ::Brick::Mixin::Colors
10
+
11
+ @@waiting_pool = []
12
+
13
+ attr_accessor :client, :name, :links, :service_config_hash, :container, :volumes_from, :image, :image_name
14
+
15
+ def self.wait_for_deamon
16
+ @@waiting_pool.each{|thr|
17
+ thr.join
18
+ }
19
+ end
20
+
21
+
22
+ def initialize(name, config, client)
23
+ self.name = name
24
+ self.service_config_hash = config
25
+ self.client = client
26
+ #puts "client=#{client}"
27
+
28
+ determine_color
29
+
30
+ unless config["links"].nil?
31
+ if config["links"].instance_of?(String)
32
+ self.links= [config["links"]]
33
+ else
34
+ self.links= config["links"].dup
35
+ end
36
+ end
37
+
38
+ unless config["volumes_from"].nil?
39
+ if config["volumes_from"].instance_of?(String)
40
+ self.volumes_from= [config["volumes_from"]]
41
+ else
42
+ self.volumes_from= config["volumes_from"].dup
43
+ end
44
+ end
45
+
46
+ begin
47
+ self.container = ::Docker::Container.get(name)
48
+ rescue
49
+ self.container = nil
50
+ end
51
+
52
+ end
53
+
54
+ def update_volumes_from services
55
+
56
+ new_volumes_from_config = []
57
+
58
+ new_volumes_from = []
59
+
60
+ unless volumes_from.nil?
61
+
62
+ volumes_from.each {|vo|
63
+ #new_volumes_from << services[vo]
64
+ vo_parts = vo.split(':')
65
+
66
+ #only one part
67
+ if vo_parts.size == 1
68
+ new_vo = "#{services[vo_parts[0]].name}:rw"
69
+ else
70
+ new_vo= "#{services[vo_parts[0]].name}:#{vo_parts[1]}"
71
+ end
72
+
73
+ new_volumes_from<< services[vo_parts[0]]
74
+
75
+ new_volumes_from_config << new_vo
76
+
77
+ }
78
+ self.volumes_from = new_volumes_from
79
+
80
+ service_config_hash["volumes_from"] = new_volumes_from_config
81
+ end
82
+ end
83
+
84
+
85
+ def update_links services
86
+
87
+ new_links_config = []
88
+
89
+ new_links =[]
90
+
91
+ unless links.nil?
92
+ links.each{|link|
93
+
94
+ link_array=link.split(':')
95
+
96
+ #It's for getting the real service name
97
+ service_key = link_array[0]
98
+
99
+ alias_name = link_array[-1]
100
+
101
+ service_container= services[service_key]
102
+
103
+ new_links << service_container
104
+
105
+ new_links_config << "#{service_container.name}:#{alias_name}"
106
+ }
107
+
108
+ self.links=new_links
109
+
110
+ service_config_hash["links"] = new_links_config
111
+ end
112
+
113
+
114
+ end
115
+
116
+ def exec cmd_array, options ={}
117
+ if self.container.nil?
118
+ raise "no container #{name} running, so we can't execute "
119
+ end
120
+
121
+ self.container.exec(cmd_array, options){|stream, chunk| puts "#{color_generator(name)} | #{chunk}".chomp }
122
+
123
+ end
124
+
125
+ #equals to "docker run"
126
+ def run enable_link=true, recreate=true, detach_mode=false
127
+
128
+ if running? and (!recreate or can_be_skipped_this_time?)
129
+ Brick::CLI::logger.debug "the service #{name} is already running. exited."
130
+ unless detach_mode
131
+ attach
132
+ end
133
+
134
+ return
135
+ end
136
+
137
+ unless volumes_from.nil?
138
+ volumes_from.each{|vo| vo.run enable_link}
139
+ end
140
+
141
+ config_hash = @service_config_hash.dup
142
+
143
+ if enable_link and !links.nil?
144
+ links.each{|linked_service|
145
+ linked_service.run enable_link
146
+
147
+ unless detach_mode
148
+ linked_service.attach
149
+ else
150
+ puts "Service #{linked_service.name} has been started"
151
+ end
152
+ }
153
+ end
154
+
155
+ if !enable_link
156
+ config_hash.delete("links")
157
+ end
158
+
159
+ if recreate and !container.nil?
160
+ #if recreate is true, it will destory the old container, and create a new one
161
+ if running?
162
+ container.stop
163
+ end
164
+ container.delete(:force => true)
165
+ self.container=nil
166
+ skip_next_time
167
+ end
168
+
169
+ if container.nil?
170
+ self.container = client.run config_hash, name
171
+ else
172
+ container.start
173
+ end
174
+
175
+ unless detach_mode
176
+ attach
177
+ else
178
+ puts "Service #{name} has been started"
179
+ end
180
+
181
+ end
182
+
183
+ #Check if the container is running
184
+ def running?
185
+ is_running = false
186
+
187
+ if container.nil?
188
+ begin
189
+ self.container = ::Docker::Container.get(name)
190
+ rescue
191
+ self.container = nil
192
+ end
193
+ end
194
+
195
+ unless container.nil?
196
+ is_running = container.is_running?
197
+ end
198
+
199
+ is_running
200
+ end
201
+
202
+ def container_info
203
+ (client.get_container_by_id(container.id)).info rescue {}
204
+ end
205
+
206
+
207
+ def attach
208
+
209
+ thr=Thread.new{
210
+ puts "Attaching to service #{name}"
211
+ container.attach(:stdin => STDIN, :tty => true){|message|
212
+
213
+ if message.length > 0
214
+ puts "#{color_generator(name)} | #{message}".chomp
215
+ end
216
+
217
+ }
218
+ }
219
+
220
+ #thr.join
221
+ @@waiting_pool << thr
222
+ end
223
+
224
+
225
+ def can_be_built?
226
+ !service_config_hash["build"].nil?
227
+ end
228
+
229
+ def skip_next_time
230
+ @skip = true
231
+ end
232
+
233
+ def can_be_skipped_this_time?
234
+ @skip == true
235
+ end
236
+
237
+ def build name=nil, no_cache=false, project_dir=nil
238
+
239
+ if name.nil?
240
+ name = self.image_name
241
+ end
242
+
243
+ puts "Start building #{name}..."
244
+
245
+ if can_be_built?
246
+ self.image = client.build_from_dir({:image_name => name,
247
+ :no_cache => no_cache,
248
+ :project_dir=>project_dir,
249
+ :build_dir=>service_config_hash["build"]})
250
+ else
251
+ Brick::CLI::logger.debug "no build defintion for #{image_build},skip it"
252
+ end
253
+ self.image
254
+ end
255
+
256
+ def image_exist?
257
+ ::Docker::Image.exist?(image_name)
258
+ end
259
+
260
+ def container_exist?
261
+ ::Docker::Container.exist?(name)
262
+ end
263
+
264
+ #If it's using build tag, will create an actual image name for it.
265
+ #For example, if project name is test, service name is web, the image name should
266
+ #be test_web
267
+ def update_image_for_building_tag name
268
+ unless service_config_hash["build"].nil?
269
+ service_config_hash["image"]=name
270
+ end
271
+
272
+ self.image_name = service_config_hash["image"]
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,13 @@
1
+ module Brick
2
+ module Models
3
+ class ServicesNotFoundException < RuntimeError
4
+
5
+ end
6
+ end
7
+ end
8
+
9
+ #BKm = Brick::Models
10
+
11
+ require 'brick/models/service'
12
+
13
+ require 'brick/models/project'