fidoci 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/bin/d +5 -5
  3. data/lib/fidoci/env.rb +256 -23
  4. data/lib/fidoci/main.rb +24 -15
  5. data/lib/fidoci.rb +2 -0
  6. metadata +17 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c0a4c8ac5bb0ee9815e811ee7ac2008b5151522
4
- data.tar.gz: 3e5346e307739072c6d5033637486a36e30bc035
3
+ metadata.gz: 50e39b8284a7f267079d82b4386bb5a7d0ba8727
4
+ data.tar.gz: 244499f405776ab68b08560320d94a004047f0fd
5
5
  SHA512:
6
- metadata.gz: 049af0b54f5a8103fd900643ad3a2a0e34be8ea587ac6bc464dc1370b18e04939acb77deef10e515a46323ce9d41e562921d92157c9ad8a46fa38224bacd0199
7
- data.tar.gz: 1d26968117084c495dc55b1c2613a5397812c9ec4100e4f5b3a5e1f66f6949a8555eff0bebc59226cbf30d846c13d1a79272e38f607e0332d0d6b90a6c522892
6
+ metadata.gz: 93563c1ab7a9f6e6e725aa1c3c9a4220a0fce1c5f7fc26a649667771bef1f1b09dbdaeb19d36c11db8c1ecdf88dcc0d4be0502817e38e8d21f5276fca7cade9b
7
+ data.tar.gz: 3674e2bf7980ad3cf17a362e505323fd2bd53ef51b0db85d9fd2aea65bb4555bc6a8de2c753fbe0b61bf275f82bd1062c818de9bebbe460a3f69f9f1ccf61f62
data/bin/d CHANGED
@@ -10,8 +10,8 @@ if ARGV.size == 0
10
10
  puts
11
11
  puts "Options:"
12
12
  puts
13
- puts " --build [--clean] tag Builds 'test' image, run tests inside and if successful, tag image by given 'tag'"
14
- puts " --clean Removes all containers and images from docker related to this repo"
13
+ puts " --build success-tag [build-id] Builds 'build_id' image, run tests inside and if successful, tag image by given 'tag'"
14
+ puts " --clean Removes all containers and images from docker related to this repo"
15
15
  puts
16
16
  puts "Command:"
17
17
  puts " Running with command will build 'exec' image, start docker-compose with that image and run given command in container"
@@ -31,10 +31,10 @@ when '--clean'
31
31
  # clean service and intermediate docker images
32
32
  environment.clean
33
33
  when '--build'
34
- # d --build [--clean] latest-staging
34
+ # d --build success-tag build-id
35
35
  # build image, test it and if successful, tag as latest-staging
36
36
  # if --clean is present, will clean all intermediate docker images after
37
- if environment.build(ARGV.last, ARGV.size > 1 && ARGV[1] == '--clean')
37
+ if environment.build(ARGV.drop(1)[0], ARGV.drop(1)[1])
38
38
  exit 0
39
39
  else
40
40
  exit 1
@@ -43,5 +43,5 @@ else
43
43
  # d cmd args args
44
44
  # run cmd in exec environment
45
45
  # build container image and run all services if not yet
46
- environment.cmd(*ARGV)
46
+ exit environment.cmd(*ARGV)
47
47
  end
data/lib/fidoci/env.rb CHANGED
@@ -10,60 +10,293 @@ module Fidoci
10
10
  @env_config = env_config
11
11
  end
12
12
 
13
- def build_image()
14
- if image_exists?
15
- puts "Using exisitng image #{image_name}..."
16
- return true
13
+ def debug(msg)
14
+ puts msg if ENV['DEBUG']
15
+ end
16
+
17
+ def info(msg)
18
+ puts msg
19
+ end
20
+
21
+ def build_image
22
+ image = Docker::Image.get(image_name) rescue nil
23
+ unless image
24
+ image = do_build_image
17
25
  end
18
26
 
19
- puts "Building image #{image_name}..."
20
- params = []
27
+ image
28
+ end
29
+
30
+ def do_build_image
31
+ info "Building image #{image_name}..."
32
+ params = {}
21
33
 
22
- params << '--force-rm=true'
23
- params << "-t #{image_name}"
34
+ params['forcerm'] = true
35
+ params['t'] = image_name
24
36
  if env_config['dockerfile']
25
- params << "-f #{env_config['dockerfile']}"
37
+ params['dockerfile'] = env_config['dockerfile']
38
+ end
39
+
40
+ last_intermediate_image = nil
41
+ image = Docker::Image.build_from_dir('.', params) do |chunk|
42
+ json = JSON.parse(chunk)
43
+ if (json['stream'] =~ /\ --->\ ([a-f0-9]{12})/) == 0
44
+ last_intermediate_image = $1
45
+ end
46
+ $stdout << json['stream']
26
47
  end
48
+ last_intermediate_image = nil
27
49
 
28
- system "docker build #{params.join(' ')} ."
50
+ image
51
+ ensure
52
+ begin
53
+ if last_intermediate_image
54
+ img = Docker::Image.get(last_intermediate_image)
55
+ img.json
56
+ puts "Removing intermediate image #{last_intermediate_image}"
57
+ img.remove('force' => true)
58
+ end
59
+ rescue
60
+ nil
61
+ end
29
62
  end
30
63
 
31
64
  def image_name
32
65
  "%s:%s" % [@image, @name]
33
66
  end
34
67
 
68
+ def tag_image(tag)
69
+ image = Docker::Image.get(image_name)
70
+ image.tag(@image => tag)
71
+ end
72
+
73
+ def container_name
74
+ @image.gsub(/[^a-zA-Z0-9_]/, '_') + "_" + @name.gsub(/[^a-zA-Z0-9_]/, '_')
75
+ end
76
+
35
77
  def clean_image!
36
- system "docker rmi -f #{image_name}"
78
+ begin
79
+ debug "Cleaning image #{image_name}"
80
+ image = Docker::Image.get(image_name)
81
+ image.remove(force: true)
82
+ rescue
83
+ true
84
+ end
85
+ end
86
+
87
+ def clean_container(cname)
88
+ begin
89
+ debug "Cleaning container #{cname}..."
90
+ container = Docker::Container.get(cname)
91
+ container.remove(force: true)
92
+ rescue
93
+ debug "Cleaning failed"
94
+ nil
95
+ end
96
+ end
97
+
98
+ def links
99
+ env_config['links'] || []
37
100
  end
38
101
 
39
- def image_exists?
40
- images = `docker images`
41
- images_names = images.split("\n").drop(1).map{|line|
42
- parts = line.split(/\s+/)
43
- parts[0..1].join(':')
102
+ def clean_containers!
103
+ containers = [container_name]
104
+ containers += links.map{|key, config| link_container_name(key) }
105
+
106
+ containers.each{|cname|
107
+ clean_container(cname)
44
108
  }
109
+ true
110
+ end
45
111
 
46
- images_names.any?{|i| i == image_name}
112
+ # clean images and containers associated with this Env
113
+ def clean!
114
+ clean_containers! rescue nil
115
+ clean_image! rescue nil
116
+ end
117
+
118
+ # stop services and clean main container
119
+ def stop!
120
+ links.map{|key, config|
121
+ cname = link_container_name(key)
122
+ begin
123
+ debug "Stopping container #{cname}..."
124
+ container = Docker::Container.get(cname)
125
+ container.stop!
126
+ rescue
127
+ nil
128
+ end
129
+ }
47
130
  end
48
131
 
132
+ # run command in docker, building the image and starting services first
133
+ # if needed
49
134
  def cmd(*args)
50
- if build_image
51
- puts "Running `#{args.join(' ')}`..."
52
- system "docker-compose run --rm #{@name} #{args.join(' ')}"
135
+ if build_image && link_containers
136
+ debug "Running `#{args.join(' ')}`..."
137
+ docker_run_or_exec(*args)
53
138
  else
54
- puts "Build failed"
139
+ debug "Build failed"
55
140
  return false
56
141
  end
57
142
  end
58
143
 
144
+ def link_container_name(key)
145
+ "#{container_name}_#{key}"
146
+ end
147
+
148
+ # starts link containers and return dict name:container_name
149
+ def link_containers
150
+ links = env_config['links'] || []
151
+
152
+ links.map {|key, link_config|
153
+ [key, start_link(link_container_name(key), link_config)]
154
+ }
155
+ end
156
+
157
+ def start_link(link_container_name, link_config)
158
+ container = Docker::Container.get(link_container_name) rescue nil
159
+
160
+ unless container
161
+ params = {}
162
+ params['name'] = link_container_name
163
+ params['Image'] = link_config['image']
164
+
165
+ config_params(params, link_config)
166
+
167
+ debug "Creating container #{link_container_name}..."
168
+ container = Docker::Container.create(params)
169
+ end
170
+
171
+ unless container.json['State']['Running']
172
+ debug "Starting container #{link_container_name}..."
173
+ container.start!
174
+ end
175
+
176
+ debug "Using container #{link_container_name}..."
177
+
178
+ link_container_name
179
+ end
180
+
181
+ def docker_run_or_exec(*args)
182
+ container = Docker::Container.get(container_name) rescue nil
183
+
184
+ if container
185
+ docker_exec(container.id, *args)
186
+ else
187
+ docker_run(*args)
188
+ end
189
+ end
190
+
191
+ def docker_exec(id, *args)
192
+ params = {}
193
+ params["Container"] = id
194
+ params["AttachStdin"] = true
195
+ params["AttachStdout"] = true
196
+ params["AttachStderr"] = true
197
+ params["Tty"] = true
198
+ params["Cmd"] = args
199
+
200
+ docker_exec = Docker::Exec.create(params)
201
+ result = docker_exec.start!(tty: true, stdin: $stdin) do |msg|
202
+ $stdout << msg
203
+ end
204
+
205
+ debug "Exited with status #{result[2]}"
206
+
207
+ result[2]
208
+ end
209
+
210
+ # calls docker run command with all needed parameters
211
+ # attaches stdin and stdout
212
+ def docker_run(*args)
213
+ params = {}
214
+ params['AttachStdin'] = true
215
+ params['OpenStdin'] = true
216
+ params['Tty'] = true
217
+ params['name'] = container_name
218
+
219
+ # links
220
+ link_containers.each{|name, container_name|
221
+ params['HostConfig'] ||= {}
222
+ params['HostConfig']['Links'] ||= []
223
+ params['HostConfig']['Links'] << "#{container_name}:#{name}"
224
+ }
225
+
226
+ config_params(params, env_config)
227
+ params['Image'] = image_name
228
+ params['Cmd'] = args
229
+
230
+ #puts params
231
+
232
+ container = Docker::Container.create(params)
233
+
234
+ container.start!.attach(stdin: $stdin, tty: true) do |msg|
235
+ $stdout << msg
236
+ end
237
+
238
+ status = container.json['State']['ExitCode']
239
+ debug "Exited with status #{status}"
240
+
241
+ status
242
+ ensure
243
+ clean_container(container_name)
244
+ end
245
+
246
+ def config_params(params, config)
247
+ params['HostConfig'] ||= {}
248
+
249
+ # env
250
+ if config['environment']
251
+ config['environment'].each {|key,val|
252
+ params['Env'] ||= []
253
+ params['Env'] << "#{key}=#{val}"
254
+ }
255
+ end
256
+
257
+ # volumes
258
+ if config['volumes']
259
+ config['volumes'].each {|v|
260
+ params['HostConfig']['Binds'] ||= []
261
+
262
+ host_path, container_path = v.split(':')
263
+ host_path = File.expand_path(host_path)
264
+
265
+ params['HostConfig']['Binds'] << "#{host_path}:#{container_path}"
266
+ }
267
+ end
268
+
269
+ # ports
270
+ if config['ports']
271
+ config['ports'].each {|p|
272
+ parts = p.split(':')
273
+ container_port = parts.last
274
+ host_port = parts[-2]
275
+ host_ip = parts[-3] || ""
276
+
277
+ params['HostConfig']['PortBindings'] ||= {}
278
+ params['HostConfig']['PortBindings']["#{container_port}/tcp"] = [
279
+ {
280
+ "HostIp" => host_ip,
281
+ "HostPort" => host_port
282
+ }
283
+ ]
284
+ }
285
+ end
286
+
287
+ params
288
+ end
289
+
59
290
  def commands
60
291
  return false unless env_config['commands']
61
292
 
62
293
  success = env_config['commands'].all? { |command|
63
- cmd command.split(/\s+/)
294
+ state = cmd(*command.split(/\s+/))
295
+ info "Exited with state #{state}"
296
+
297
+ state == 0
64
298
  }
65
299
 
66
- puts "Test failed" unless success
67
300
  success
68
301
  end
69
302
  end
data/lib/fidoci/main.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'yaml'
2
+ require 'securerandom'
3
+ require 'docker'
2
4
 
3
5
  module Fidoci
4
6
  # Main entry point for D command
@@ -11,14 +13,20 @@ module Fidoci
11
13
  # config_file - yml file path with configuration
12
14
  def initialize(config_file = 'd.yml')
13
15
  @config = YAML.load_file(config_file)
14
- puts @config['image']
16
+
17
+ Docker.options = {
18
+ read_timeout: 3600
19
+ }
15
20
  end
16
21
 
17
22
  # Run command in default "exec" environment
18
23
  # ie in container build and started with exec configuration
19
24
  # args - command and arguments to pass to docker run command
20
25
  def cmd(*args)
21
- env(:exec).cmd(*args)
26
+ exec_env = env(:dev, 'dev')
27
+ exec_env.cmd(*args)
28
+ ensure
29
+ exec_env.stop!
22
30
  end
23
31
 
24
32
  # Configured docker repository name
@@ -29,38 +37,39 @@ module Fidoci
29
37
 
30
38
  # Create environment instance with given name
31
39
  # name - key that will be used to configure this env
32
- def env(name)
33
- Env.new(repository_name, name.to_s, config[name.to_s])
40
+ # id - unique identifier of env that will be used to tag containers and images
41
+ def env(name, id)
42
+ Env.new(repository_name, id.to_s, config[name.to_s])
34
43
  end
35
44
 
36
45
  # Clean system
37
46
  # removes all service and running containers and their images
38
47
  # and removes all images build by d
39
48
  def clean
40
- system 'docker-compose kill'
41
- system 'docker-compose rm -f'
42
-
43
49
  (config.keys - ['image']).each { |name|
44
- env = env(name)
45
- env.clean_image!
50
+ env = env(name, name)
51
+ env.clean!
46
52
  }
47
53
  end
48
54
 
49
55
  # Build image and run test in it
50
56
  # tag - tag name to tag image after successful build and test
51
- # do_clean - if true, will do clean after build (whether successful or not)
52
- def build(tag, do_clean = false)
53
- test_env = env(:test)
54
- test_env.clean_image!
57
+ # build_id - unique build_id to be used to identify docker images and containers
58
+ def build(tag, build_id)
59
+ build_id = SecureRandom.hex(10) unless build_id
60
+
61
+ test_env = env(:build, build_id)
62
+ test_env.clean!
63
+
55
64
  success = test_env.commands
56
65
 
57
66
  if success
58
- system "docker tag #{test_env.image_name} #{repository_name}:#{tag}"
67
+ test_env.tag_image(tag)
59
68
  end
60
69
 
61
70
  success
62
71
  ensure
63
- clean if do_clean
72
+ test_env.clean! if test_env
64
73
  end
65
74
  end
66
75
  end
data/lib/fidoci.rb CHANGED
@@ -1,5 +1,7 @@
1
+ require 'docker'
1
2
  require 'fidoci/env'
2
3
  require 'fidoci/main'
3
4
 
4
5
  module Fidoci
6
+
5
7
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fidoci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas Dolezal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-10 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2015-07-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: docker-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Simple tool around docker-compose to enable dockerized dev-2-test-2-production
14
28
  workflow
15
29
  email: lukas@dolezalu.cz