docker_brick 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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'