fidoci 0.1.1 → 0.1.2

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 (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