docker-rails 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29eddb9dd688e2781429ec0fa43faa2eb5797a60
4
- data.tar.gz: fbcb78c9a799b9a7bae895a5727904d344f7a9a1
3
+ metadata.gz: 5ee7498a73d43a8cc24e329c9bdf688e5bde379f
4
+ data.tar.gz: 817e09489010ddcc26b6783335ee5d88281e016d
5
5
  SHA512:
6
- metadata.gz: ee49cf865269e985ba43848ec8cfe3ceeac29fc58802d4b44b0631ac5065e1f22d26767aff4abaf6619f5a804a0f7baeb1437711d85860c75ab31b7c54e28837
7
- data.tar.gz: 44911f7657c234530b003e41f46435862a832ba024553cf8e19bb4869e19ddd2c33a9c159cc7c163a8937fd76b6d134c4f9c8ce3e845d9e91221274747f75bc8
6
+ metadata.gz: 753d087b003e69125829c8f3f7ebfd88e4b36b838b3041078c6f8c7a6b68d59054a02c7e57c71f797ff93654786c535b44576a17dc2ab2da38a0029f598d2d84
7
+ data.tar.gz: b7cf55c4a143394e20f1a80ed918cdb60d014f9368e549de8bcf6368963bfb5515c1790fdebd5e30e3e75b36c34bc6fca3ac1209d1ab166cfbe3a08213eeba56
data/README.md CHANGED
@@ -13,6 +13,7 @@ A simplified pattern to execute rails applications within Docker (with a CI buil
13
13
  - Interpolates variables `docker-compose.yml` making CI builds much easier
14
14
  - DB check CLI function provided for docker-compose `command` to check if db is ready
15
15
  - Configurable exit_code for `ci` - determine which container's exit code will be the result of the process (useful for CI tests)
16
+ - Declarative ssh key sharing via SSH Agent Forwarding, including making `known_hosts` available to targeted containers.
16
17
 
17
18
  ## Usage
18
19
 
@@ -28,10 +29,11 @@ CI, the reason this is built. Do it all, do it consistently, do it concurrently,
28
29
 
29
30
  1. `before_command` - run anything on the host prior to building the docker image e.g. `rm -Rf target`
30
31
  2. `compose` - create the resolved `docker-compose.yml`
31
- 3. `gemset_volume` - find or create the shared global gems volume for this ruby version
32
- 4. `build` - `docker-compose build` the configuration
33
- 5. `up` - `docker-compose up` the configuration
34
- 6. `cleanup`
32
+ 3. `gemset_volume create` - find or create the shared global gems volume for this ruby version
33
+ 4. `ssh_agent forward` - forward any declared keys and copy in `known_hosts`
34
+ 5. `build` - `docker-compose build` the configuration
35
+ 6. `up` - `docker-compose up` the configuration
36
+ 7. `cleanup`
35
37
  1. `stop` - stop all containers for this configuration (including one-off sessions)
36
38
  2. `extract` - extract any defined files from any container
37
39
  3. `rm_volumes` - `docker-compose rm -v --force` to cleanup any container volumes (excluding the gems volume)
@@ -72,21 +74,22 @@ Almost all of the commands below are in support of the `ci` command, so why not
72
74
 
73
75
  ```bash
74
76
  Commands:
75
- docker-rails bash_connect <target> <service_name> # Open a bash shell to a running container (with automatic cleanup) e.g. bundle exec docker-rails bash --build=222 development db
76
- docker-rails build <target> # Build for the given build/target e.g. bundle exec docker-rails build --build=222 development
77
- docker-rails ci <target> # Execute the works, everything with cleanup included e.g. bundle exec docker-rails ci --build=222 test
78
- docker-rails cleanup <target> # Runs container cleanup functions stop, rm_volumes, rm_compose, rm_dangling, ps_all e.g. bundle exec docker-rails cleanup --build=222 development
79
- docker-rails compose <target> # Writes a resolved docker-compose.yml file e.g. bundle exec docker-rails compose --build=222 test
77
+ docker-rails bash_connect <target> <service_name> # Open a bash shell to a running container (with automatic cleanup) e.g. docker-rails bash --build=222 development db
78
+ docker-rails build <target> # Build for the given build/target e.g. docker-rails build --build=222 development
79
+ docker-rails ci <target> # Execute the works, everything with cleanup included e.g. docker-rails ci --build=222 test
80
+ docker-rails cleanup <target> # Runs container cleanup functions stop, rm_volumes, rm_compose, rm_dangling, ps_all e.g. docker-rails cleanup --build=222 development
81
+ docker-rails compose <target> # Writes a resolved docker-compose.yml file e.g. docker-rails compose --build=222 test
80
82
  docker-rails db_check <db> # Runs db_check e.g. bundle exec docker-rails db_check mysql
81
- docker-rails exec <target> <service_name> <command> # Run an arbitrary command on a given service container e.g. bundle exec docker-rails exec --build=222 development db bash
82
- docker-rails gemset_volume <command> # Gems volume management e.g. bundle exec docker-rails gemset_volume create
83
+ docker-rails exec <target> <service_name> <command> # Run an arbitrary command on a given service container e.g. docker-rails exec --build=222 development db bash
84
+ docker-rails gemset_volume <command> # Gemset volume management e.g. docker-rails gemset_volume create
83
85
  docker-rails help [COMMAND] # Describe available commands or one specific command
84
- docker-rails ps <target> # List containers for the target compose configuration e.g. bundle exec docker-rails ps --build=222 development
85
- docker-rails ps_all # List all remaining containers regardless of state e.g. bundle exec docker-rails ps_all
86
- docker-rails rm_dangling # Remove danging images e.g. bundle exec docker-rails rm_dangling
87
- docker-rails rm_volumes <target> # Stop all running containers and remove corresponding volumes for the given build/target e.g. bundle exec docker-rails rm_volumes --build=222 development
88
- docker-rails stop <target> # Stop all running containers for the given build/target e.g. bundle exec docker-rails stop --build=222 development
89
- docker-rails up <target> # Up the docker-compose configuration for the given build/target. Use -d for detached mode. e.g. bundle exec docker-rails up -d --build=222 test
86
+ docker-rails ps <target> # List containers for the target compose configuration e.g. docker-rails ps --build=222 development
87
+ docker-rails ps_all # List all remaining containers regardless of state e.g. docker-rails ps_all
88
+ docker-rails rm_dangling # Remove danging images e.g. docker-rails rm_dangling
89
+ docker-rails rm_volumes <target> # Stop all running containers and remove corresponding volumes for the given build/target e.g. docker-rails rm_volumes --build=222 development
90
+ docker-rails ssh_agent <command> # SSH Agent Forwarding e.g. docker-rails ssh_agent forward
91
+ docker-rails stop <target> # Stop all running containers for the given build/target e.g. docker-rails stop --build=222 development
92
+ docker-rails up <target> # Up the docker-compose configuration for the given build/target. Use -d for detached mode. e.g. docker-rails up -d --build=222 test
90
93
 
91
94
  Options:
92
95
  -b, [--build=BUILD] # Build name e.g. 123. Can also be specified as environment variable DOCKER_RAILS_BUILD
@@ -141,13 +144,14 @@ COPY . /project
141
144
  ### 2. Add a docker-rails.yml
142
145
 
143
146
  Environment variables will be interpolated, so feel free to use them.
144
- The _rails engine_ example below shows an example with all of the environments `development | test | parallel_tests | staging` to show reuse of the primary `compose` configuration.
147
+ The _rails engine_ example below shows an example with all of the environments `ssh_test | development | test | parallel_tests | staging` to show reuse of the primary `compose` configuration.
145
148
 
146
149
  ```yaml
147
150
  verbose: true
148
151
  exit_code: web
149
152
  before_command: bash -c "rm -Rf target && rm -Rf spec/dummy/log"
150
153
 
154
+ # ---
151
155
  # create a global gemset to be shared amongst all ruby 2.2.2 containers.
152
156
  gemset:
153
157
  name: 2.2.2
@@ -155,6 +159,18 @@ gemset:
155
159
  containers:
156
160
  - web
157
161
 
162
+ # ---
163
+ # Make the host user's id_rsa key available to the web container e.g. for cloning from github
164
+ # If you see "Host key verification failed", make sure the same command runs on the host first
165
+ # which will add to the known_hosts file. The known_hosts is copied from the host to the ssh-agent automatically.
166
+ ssh-agent:
167
+ containers:
168
+ - web
169
+ keys:
170
+ - id_rsa
171
+
172
+ # ---
173
+ # Declare a reusable extract set
158
174
  extractions: &extractions
159
175
  web:
160
176
  extract:
@@ -165,12 +181,42 @@ extractions: &extractions
165
181
  - '/project/tmp/parallel_runtime_rspec.log:./tmp'
166
182
 
167
183
 
168
- # local environments need elasticsearch, staging/production connects to existing running instance.
184
+ # ---
185
+ # Declare a reusable elasticsearch container, staging/production connects to existing running instance.
169
186
  elasticsearch: &elasticsearch
170
187
  elasticsearch:
171
188
  image: library/elasticsearch:1.7
172
189
  ports:
173
190
  - "9200"
191
+
192
+ # ---
193
+ # Base docker-compose configuration for all environments. Anything under the `compose` element must be standard docker-compose syntax.
194
+ compose:
195
+ web:
196
+ build: .
197
+ working_dir: /project/spec/dummy
198
+ ports:
199
+ - "3000"
200
+
201
+ links:
202
+ - db
203
+
204
+ db:
205
+ # https://github.com/docker-library/docs/tree/master/mysql
206
+ image: library/mysql:5.7.6
207
+ ports:
208
+ - "3306"
209
+
210
+ # https://github.com/docker-library/docs/tree/master/mysql#environment-variables
211
+ environment:
212
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
213
+
214
+ # ---
215
+ # Overrides based on the named targets ssh_test | development | test | parallel_tests | staging
216
+ ssh_test:
217
+ compose:
218
+ web:
219
+ command: bash -c "ssh -T git@bitbucket.org"
174
220
 
175
221
  development:
176
222
  compose:
@@ -290,27 +336,6 @@ staging:
290
336
  && gem install foreman
291
337
  && foreman start
292
338
  "
293
-
294
- # base docker-compose configuration for all environments
295
- compose:
296
- web:
297
- build: .
298
- working_dir: /project/spec/dummy
299
- ports:
300
- - "3000"
301
-
302
- links:
303
- - db
304
-
305
- db:
306
- # https://github.com/docker-library/docs/tree/master/mysql
307
- image: library/mysql:5.7.6
308
- ports:
309
- - "3306"
310
-
311
- # https://github.com/docker-library/docs/tree/master/mysql#environment-variables
312
- environment:
313
- - MYSQL_ALLOW_EMPTY_PASSWORD=true
314
339
  ```
315
340
 
316
341
  ## CI setup
@@ -18,5 +18,6 @@ require 'docker/rails/app'
18
18
 
19
19
  require 'docker/rails/cli/db_check'
20
20
  require 'docker/rails/cli/gemset_volume'
21
+ require 'docker/rails/cli/ssh_agent'
21
22
 
22
23
  require 'docker/rails/cli/main'
@@ -5,12 +5,6 @@ module Docker
5
5
  include Singleton
6
6
  attr_reader :config,
7
7
  :compose_config,
8
- :ruby_version,
9
- :build, # given build, usually a number
10
- :project_name, # resolved compose project name
11
- :target,
12
- :gemset_volume_path,
13
- :gemset_volume_name,
14
8
  :compose_filename,
15
9
  :exit_code
16
10
 
@@ -30,35 +24,22 @@ module Docker
30
24
  end
31
25
 
32
26
  def configure(options)
33
- @target = options[:target]
34
-
35
27
  # Allow CLI option `build` to fallback to an env variable DOCKER_RAILS_BUILD. Note that CLI provides a default build value of 1, so check against the default and existence of the env var.
36
28
  build = options[:build]
37
29
  build = ENV['DOCKER_RAILS_BUILD'] if build.to_i == 1 && !ENV['DOCKER_RAILS_BUILD'].nil?
38
- ENV['DOCKER_RAILS_BUILD'] = @build = build
39
-
40
- # determine project_name
41
- dir_name = Dir.pwd.split('/').last
42
- @project_name = "#{dir_name}_#{target}_#{build}"
43
-
44
- # FIXME: temporarily sanitize project_name until they loosen restrictions see https://github.com/docker/compose/issues/2119
45
- @project_name = @project_name.gsub(/[^a-z0-9]/, '')
30
+ ENV['DOCKER_RAILS_BUILD'] = build
46
31
 
32
+ target = options[:target]
47
33
 
48
34
  # load the docker-rails.yml
49
- @config = Docker::Rails::Config.new
50
- @config.load!(@target)
51
-
52
- # these are generated/resolved in the config#load, grab them for convenience
53
- @gemset_volume_path = ENV['DOCKER_RAILS_GEMSET_VOLUME_PATH']
54
- @gemset_volume_name = ENV['DOCKER_RAILS_GEMSET_VOLUME_NAME']
55
-
35
+ @config = Docker::Rails::Config.new({build: build, target: target})
36
+ @config.load!(target)
56
37
  @is_configured = true
57
38
  end
58
39
 
59
40
  def compose
60
41
  # Write a docker-compose.yml with interpolated variables
61
- @compose_filename = compose_filename_from @project_name
42
+ @compose_filename = compose_filename_from project_name
62
43
 
63
44
  rm_compose
64
45
 
@@ -79,7 +60,7 @@ module Docker
79
60
  @is_configured || false
80
61
  end
81
62
 
82
- def extract
63
+ def extract_all
83
64
 
84
65
  # For each container, process extractions
85
66
  # Containers are defined in compose, extractions are defined at root under container name e.g.:
@@ -106,22 +87,26 @@ module Docker
106
87
  next
107
88
  end
108
89
 
109
- extractions.each do |extraction|
110
- if extraction =~ /:/
111
- tokens = extraction.split(':')
112
- from = tokens[0]
113
- to = tokens[1]
114
- else
115
- from = extraction
116
- to = '.'
117
- end
90
+ extract(container, service_name, extractions)
91
+ end
92
+ end
118
93
 
119
- puts "\nExtracting #{service_name} #{from} to #{to}"
120
- begin
121
- extract_files(container, from, to)
122
- rescue => e
123
- puts e.message
124
- end
94
+ def extract(container, service_name, extractions)
95
+ extractions.each do |extraction|
96
+ if extraction =~ /:/
97
+ tokens = extraction.split(':')
98
+ from = tokens[0]
99
+ to = tokens[1]
100
+ else
101
+ from = extraction
102
+ to = '.'
103
+ end
104
+
105
+ puts "\nExtracting #{service_name} #{from} to #{to}"
106
+ begin
107
+ extract_files(container, from, to)
108
+ rescue => e
109
+ puts e.message
125
110
  end
126
111
  end
127
112
  end
@@ -157,66 +142,13 @@ module Docker
157
142
  exec 'docker ps -a'
158
143
  end
159
144
 
160
- def stop
145
+ def stop_all
161
146
  puts "\n\n\n\nStopping containers..."
162
147
  puts '-----------------------------'
163
148
  containers = Docker::Container.all(all: true)
164
149
  containers.each do |container|
165
150
  if is_project_container?(container)
166
- printf "#{container.name}.."
167
-
168
- # Stop it
169
- container.stop
170
- 60.times do |i|
171
- printf '.'
172
- if container.down?
173
- printf "done.\n"
174
- break
175
- end
176
- sleep 1
177
- end
178
-
179
- # Kill it #1 - if still up, kill it softly? # http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_01.html
180
- if container.up?
181
- printf 'killing(-1)'
182
- container.kill(signal: 'SIGHUP')
183
- 10.times do |i|
184
- printf '.'
185
- if container.down?
186
- printf "done.\n"
187
- break
188
- end
189
- sleep 1
190
- end
191
- end
192
-
193
- # Kill it #2 - if still up, kill it with a vengeance? # http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_01.html
194
- if container.up?
195
- printf 'killing(-9)'
196
- container.kill(signal: 'SIGKILL')
197
- 10.times do |i|
198
- printf '.'
199
- if container.down?
200
- printf "done.\n"
201
- break
202
- end
203
- sleep 1
204
- end
205
- end
206
-
207
- # Kill it #3 - if still up, kill it with a chuck norris? # http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_01.html
208
- if container.up?
209
- printf 'killing(Chuck Norris)'
210
- container.kill(signal: 'SIGSTOP')
211
- 10.times do |i|
212
- printf '.'
213
- if container.down?
214
- printf "done.\n"
215
- break
216
- end
217
- sleep 1
218
- end
219
- end
151
+ stop(container)
220
152
 
221
153
  service_name = container.compose.service
222
154
  if @config['exit_code'].eql?(service_name)
@@ -229,6 +161,7 @@ module Docker
229
161
  end
230
162
  end
231
163
  end
164
+ rm_ssh_agent
232
165
  puts 'Done.'
233
166
  end
234
167
 
@@ -241,7 +174,7 @@ module Docker
241
174
  containers.each do |container|
242
175
  if is_project_container?(container)
243
176
  puts container.name
244
- container.remove(v: true, force: true)
177
+ rm_v(container)
245
178
  end
246
179
  end
247
180
  puts 'Done.'
@@ -268,7 +201,7 @@ module Docker
268
201
  # docker exec -it 2ed97d0bb938 bash
269
202
  container = get_container(service_name)
270
203
  if container.nil?
271
- puts "#{service_name} does not appear to be running for build #{@build}"
204
+ puts "#{service_name} does not appear to be running for build #{build}"
272
205
  return
273
206
  end
274
207
 
@@ -276,16 +209,43 @@ module Docker
276
209
  container
277
210
  end
278
211
 
212
+ def run_ssh_agent
213
+ return if @config[:'ssh-agent'].nil?
214
+ run_ssh_agent_daemon
215
+ ssh_add_keys
216
+ ssh_add_known_hosts
217
+ end
218
+
219
+ def rm_ssh_agent
220
+ ssh_agent_name = @config.ssh_agent_name
221
+ begin
222
+ container = Docker::Container.get(ssh_agent_name)
223
+ stop(container)
224
+ rm_v(container)
225
+ rescue Docker::Error::NotFoundError => e
226
+ puts "SSH Agent forwarding container #{ssh_agent_name} does not exist."
227
+ end
228
+ end
229
+
279
230
  # Create global gems data volume to cache gems for this version of ruby
280
231
  # https://docs.docker.com/userguide/dockervolumes/
281
- def create_gems_volume
232
+ def create_gemset_volume
282
233
  begin
283
- Docker::Container.get(@gemset_volume_name)
284
- puts "Gem data volume container #{@gemset_volume_name} already exists."
234
+ Docker::Container.get(gemset_volume_name)
235
+ puts "Gem data volume container #{gemset_volume_name} already exists."
285
236
  rescue Docker::Error::NotFoundError => e
286
237
 
287
- exec "docker create -v #{@gemset_volume_path} --name #{@gemset_volume_name} busybox"
288
- puts "Gem data volume container #{@gemset_volume_name} created."
238
+ exec "docker create -v #{gemset_volume_path} --name #{gemset_volume_name} busybox"
239
+ puts "Gem data volume container #{gemset_volume_name} created."
240
+ end
241
+ end
242
+
243
+ def rm_gemset_volume
244
+ begin
245
+ container = Docker::Container.get(gemset_volume_name)
246
+ rm_v(container)
247
+ rescue Docker::Error::NotFoundError => e
248
+ puts "Gem data volume container #{gemset_volume_name} does not exist."
289
249
  end
290
250
  end
291
251
 
@@ -308,7 +268,7 @@ module Docker
308
268
  # in the case of running a bash session, this file may dissappear, just make sure it is there.
309
269
  compose unless File.exists?(@compose_filename)
310
270
 
311
- exec("docker-compose -f #{@compose_filename} -p #{@project_name} #{cmd} #{options}", capture, ignore_errors)
271
+ exec("docker-compose -f #{@compose_filename} -p #{project_name} #{cmd} #{options}", capture, ignore_errors)
312
272
  end
313
273
 
314
274
  def get_container(service_name)
@@ -327,7 +287,7 @@ module Docker
327
287
  # build = labels['com.docker.compose.project']
328
288
 
329
289
  return false if container.compose.nil?
330
- return true if @project_name.eql? container.compose.project
290
+ return true if project_name.eql? container.compose.project
331
291
  false
332
292
  end
333
293
 
@@ -363,6 +323,101 @@ module Docker
363
323
  end
364
324
  }
365
325
  end
326
+
327
+ def stop(container)
328
+ printf "#{container.name}.."
329
+
330
+ # Stop it
331
+ container.stop
332
+ 60.times do |i|
333
+ printf '.'
334
+ if container.down?
335
+ printf "done.\n"
336
+ break
337
+ end
338
+ sleep 1
339
+ end
340
+
341
+ # kill it if necessary
342
+ kill(container)
343
+ end
344
+
345
+ # kill container, progressively more forceful from -1, -9, then full Chuck Norris.
346
+ def kill(container)
347
+ %w(SIGHUP SIGKILL SIGSTOP).each do |signal|
348
+ if container.up?
349
+ printf "killing(#{signal})"
350
+ container.kill(signal: signal)
351
+ 10.times do |i|
352
+ printf '.'
353
+ if container.down?
354
+ printf "done.\n"
355
+ break
356
+ end
357
+ sleep 1
358
+ end
359
+ end
360
+ end
361
+ end
362
+
363
+ def ssh_agent_image
364
+ # 'whilp/ssh-agent:latest'
365
+ 'rosskevin/ssh-agent:latest'
366
+ end
367
+
368
+ def ssh_base_cmd
369
+ ssh_agent_name = @config.ssh_agent_name
370
+ "docker run --rm --volumes-from=#{ssh_agent_name} -v ~/.ssh:/ssh #{ssh_agent_image}"
371
+ end
372
+
373
+ def ssh_add_known_hosts
374
+ exec "#{ssh_base_cmd} cp /ssh/known_hosts /root/.ssh/known_hosts"
375
+ end
376
+
377
+ def ssh_add_keys
378
+ ssh_keys = @config[:'ssh-agent'][:keys]
379
+ puts "Forwarding SSH key(s): #{ssh_keys.join(',')} into container(s): #{@config[:'ssh-agent'][:containers].join(',')}"
380
+ ssh_keys.each do |key_file_name|
381
+ local_key_file = "#{ENV['HOME']}/.ssh/#{key_file_name}"
382
+ raise "Local key file #{local_key_file} doesn't exist." unless File.exists? local_key_file
383
+ exec "#{ssh_base_cmd} ssh-add /ssh/#{key_file_name}"
384
+ end
385
+ end
386
+
387
+ def run_ssh_agent_daemon
388
+ ssh_agent_name = @config.ssh_agent_name
389
+ begin
390
+ Docker::Container.get(ssh_agent_name)
391
+ puts "Gem data volume container #{ssh_agent_name} already exists."
392
+ rescue Docker::Error::NotFoundError => e
393
+ exec "docker run -d --name=#{ssh_agent_name} #{ssh_agent_image}"
394
+ puts "SSH Agent forwarding container #{ssh_agent_name} running."
395
+ end
396
+ end
397
+
398
+ def rm_v(container)
399
+ container.remove(v: true, force: true)
400
+ end
401
+
402
+ def gemset_volume_name
403
+ @config[:gemset][:volume][:name]
404
+ end
405
+
406
+ def gemset_volume_path
407
+ @config[:gemset][:volume][:path]
408
+ end
409
+
410
+ def project_name
411
+ @config[:project_name]
412
+ end
413
+
414
+ def build
415
+ @config[:build]
416
+ end
417
+
418
+ def target
419
+ @config[:target]
420
+ end
366
421
  end
367
422
  end
368
423
  end
@@ -2,16 +2,15 @@ module Docker
2
2
  module Rails
3
3
  module CLI
4
4
  class GemsetVolume < Thor
5
-
6
- default_task :help
7
-
8
- desc 'create', 'Create a gem volume'
5
+ desc 'create', 'Create a gemset volume'
9
6
  def create(target = nil)
10
- App.configured(target, options).create_gems_volume
7
+ App.configured(target, options).create_gemset_volume
11
8
  end
12
9
 
13
-
14
- # TODO: add destroy volume
10
+ desc 'rm', 'Remove a gemset volume'
11
+ def rm(target = nil)
12
+ App.configured(target, options).rm_gemset_volume
13
+ end
15
14
  end
16
15
  end
17
16
  end
@@ -12,6 +12,9 @@ module Docker
12
12
  desc 'gemset_volume <command>', 'Gemset volume management e.g. docker-rails gemset_volume create'
13
13
  subcommand 'gemset_volume', Docker::Rails::CLI::GemsetVolume
14
14
 
15
+ desc 'ssh_agent <command>', 'SSH Agent Forwarding e.g. docker-rails ssh_agent forward'
16
+ subcommand 'ssh_agent', Docker::Rails::CLI::SshAgent
17
+
15
18
  desc 'ci <target>', 'Execute the works, everything with cleanup included e.g. docker-rails ci --build=222 test'
16
19
  long_desc <<-D
17
20
 
@@ -27,6 +30,7 @@ module Docker
27
30
  invoke :before, [target], []
28
31
  invoke :compose, [target], []
29
32
  invoke CLI::GemsetVolume, :create, [target], options
33
+ invoke CLI::SshAgent, :forward, [target], options
30
34
  begin
31
35
  invoke :build # on CI - always build to ensure dockerfile hasn't been altered - small price to pay for consistent CI.
32
36
  invoke :up
@@ -42,7 +46,7 @@ module Docker
42
46
  def extract(target)
43
47
  app = App.configured(target, options)
44
48
  invoke :compose, [target], []
45
- app.extract
49
+ app.extract_all
46
50
  end
47
51
 
48
52
  desc 'cleanup <target>', 'Runs container cleanup functions stop, rm_volumes, rm_compose, rm_dangling, ps_all e.g. docker-rails cleanup --build=222 development'
@@ -67,6 +71,7 @@ module Docker
67
71
 
68
72
  invoke :before, [target], base_options
69
73
  invoke CLI::GemsetVolume, :create, [target], base_options
74
+ invoke CLI::SshAgent, :forward, [target], base_options
70
75
 
71
76
  compose_options = ''
72
77
  compose_options = '-d' if options[:detached]
@@ -99,7 +104,7 @@ module Docker
99
104
 
100
105
  def stop(target)
101
106
  invoke :compose
102
- App.configured(target, options).stop
107
+ App.configured(target, options).stop_all
103
108
  end
104
109
 
105
110
  desc 'rm_volumes <target>', 'Stop all running containers and remove corresponding volumes for the given build/target e.g. docker-rails rm_volumes --build=222 development'
@@ -157,6 +162,8 @@ module Docker
157
162
  app = App.configured(target, options)
158
163
 
159
164
  invoke :compose, [target], []
165
+ invoke CLI::GemsetVolume, :create, [target], []
166
+ invoke CLI::SshAgent, :forward, [target], []
160
167
 
161
168
  app.run_service_command(service_name, command)
162
169
  end
@@ -0,0 +1,17 @@
1
+ module Docker
2
+ module Rails
3
+ module CLI
4
+ class SshAgent < Thor
5
+ desc 'forward', 'Run SSH Agent Forwarding'
6
+ def forward(target = nil)
7
+ App.configured(target, options).run_ssh_agent
8
+ end
9
+
10
+ desc 'rm', 'Stop and remove SSH Agent Forwarding'
11
+ def rm(target = nil)
12
+ App.configured(target, options).rm_ssh_agent
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,29 +3,46 @@ module Docker
3
3
  require 'dry/config'
4
4
  class Config < Dry::Config::Base
5
5
 
6
- # environment:
7
- # # make ssh keys available via ssh forwarding (see volume entry)
8
- # - SSH_AUTH_SOCK=/ssh-agent/socket
9
- #
10
- # volumes_from:
11
- # # Use configured whilp/ssh-agent long running container for keys
12
- # - ssh-agent
13
- SSH_AGENT_DEFAULT_CONFIG = {
14
- environment: ['SSH_AUTH_SOCK=/ssh-agent/socket'],
15
- volumes_from: ['ssh-agent']
16
- }
6
+ def initialize(options = {})
7
+ raise 'build unspecified' if options[:build].nil?
8
+ build = options[:build]
17
9
 
10
+ raise 'target unspecified' if options[:target].nil?
11
+ target = options[:target]
12
+
13
+ # determine project_name
14
+ dir_name = Dir.pwd.split('/').last
15
+ project_name = "#{dir_name}_#{target}_#{build}"
16
+
17
+ # FIXME: temporarily sanitize project_name until they loosen restrictions see https://github.com/docker/compose/issues/2119
18
+ project_name = project_name.gsub(/[^a-z0-9]/, '')
18
19
 
19
- def initialize(options = {})
20
20
  super({
21
21
  default_configuration: {
22
+ build: build,
23
+ target: target,
24
+ project_name: project_name,
22
25
  verbose: false
23
-
24
26
  },
25
27
  prune: [:development, :test, :parallel_tests, :staging, :production]
26
28
  }.merge(options))
27
29
  end
28
30
 
31
+
32
+ def ssh_agent_name
33
+ "#{self[:project_name]}_ssh_agent"
34
+ end
35
+
36
+ def write_docker_compose_file(output_filename = 'docker-compose.yml')
37
+ write_yaml_file(output_filename, self[:'compose'])
38
+ end
39
+
40
+ def to_yaml(config = @configuration)
41
+ yaml = super(config)
42
+ yaml = yaml.gsub(/command: .$/, 'command: >')
43
+ yaml
44
+ end
45
+
29
46
  def load!(environment, *filenames)
30
47
 
31
48
  # reject nil target environments
@@ -38,8 +55,8 @@ module Docker
38
55
  end
39
56
 
40
57
  # reject unknown target environments
41
- config = load_unpruned(environment, *filenames)
42
- raise "Unknown target environment '#{environment.to_sym}'" if config[environment.to_sym].nil?
58
+ unpruned_config = load_unpruned(environment, *filenames)
59
+ raise "Unknown target environment '#{environment.to_sym}'" if unpruned_config[environment.to_sym].nil?
43
60
 
44
61
 
45
62
  # -----------------------------------------------------
@@ -49,54 +66,76 @@ module Docker
49
66
 
50
67
  # ----
51
68
  # ssh-agent
52
- ssh_agent = config[:'ssh-agent']
53
- if !ssh_agent.nil?
54
- ssh_agent[:containers].each do |container|
55
- raise "Unknown container #{container}" if config[:compose][container.to_sym].nil?
56
- compose[container.to_sym] ||= {}
57
- compose[container.to_sym].deeper_merge! ({}.merge SSH_AGENT_DEFAULT_CONFIG)
58
- end
59
- end
69
+ generate_ssh_agent(compose, unpruned_config)
60
70
 
61
71
  # ----
62
72
  # gemset volume
63
- gemset = config[:gemset]
73
+ generate_gemset(compose, unpruned_config, generated_defaults, filenames)
74
+
75
+ # now add the generated to the seeded default configuration
76
+ @default_configuration.merge!(generated_defaults)
77
+
78
+ # reset the base @configuration by loading the new default configuration
79
+ clear
80
+
81
+ # finally, load the config as internal state
82
+ super(environment, *filenames)
83
+ end
84
+
85
+ protected
86
+
87
+ def generate_gemset(compose, unpruned_config, generated_defaults, filenames)
88
+ gemset = unpruned_config[:gemset]
64
89
  raise "Expected to find 'gemset:' in #{filenames}" if gemset.nil?
65
90
 
66
91
  gemset_name = gemset[:name]
67
92
  raise "Expected to find 'gemset: name' in #{filenames}" if gemset_name.nil?
68
93
 
69
- ENV['DOCKER_RAILS_GEMSET_VOLUME_PATH'] = gemset_volume_path = "/gemset/#{gemset_name}"
70
- ENV['DOCKER_RAILS_GEMSET_VOLUME_NAME'] = gemset_volume_name = "gemset-#{gemset_name}"
94
+ # add the generated gemset name/path to the generated defaults
95
+ gemset_volume_path = "/gemset/#{gemset_name}"
96
+ gemset_volume_name = "gemset-#{gemset_name}"
97
+
98
+ generated_defaults.deeper_merge!(gemset: gemset)
99
+ generated_defaults[:gemset].deeper_merge!({
100
+ volume: {
101
+ name: gemset_volume_name,
102
+ path: gemset_volume_path
103
+ }
104
+
105
+ })
71
106
 
72
107
  raise "Expected to find 'gemset: containers' with at least one entry" if gemset[:containers].nil? || gemset[:containers].length < 1
73
108
  gemset[:containers].each do |container|
74
- raise "Unknown container #{container}" if config[:compose][container.to_sym].nil?
109
+ raise "Unknown container #{container}" if unpruned_config[:compose][container.to_sym].nil?
75
110
  compose[container.to_sym] ||= {}
76
111
  compose[container.to_sym].deeper_merge! ({
77
112
  environment: ["GEM_HOME=#{gemset_volume_path}"],
78
113
  volumes_from: [gemset_volume_name]
79
114
  })
80
115
  end
81
-
82
- # now add the generated to the seeded default configuration
83
- @default_configuration.merge!(generated_defaults)
84
-
85
- # reset the base @configuration by loading the new default configuration
86
- clear
87
-
88
- # finally, load the config as internal state
89
- super(environment, *filenames)
90
116
  end
91
117
 
92
- def write_docker_compose_file(output_filename = 'docker-compose.yml')
93
- write_yaml_file(output_filename, self[:'compose'])
94
- end
118
+ def generate_ssh_agent(compose, unpruned_config)
119
+ ssh_agent = unpruned_config[:'ssh-agent']
120
+ if !ssh_agent.nil?
95
121
 
96
- def to_yaml(config = @configuration)
97
- yaml = super(config)
98
- yaml = yaml.gsub(/command: .$/, 'command: >')
99
- yaml
122
+ raise "Expected to find 'ssh-agent: keys' with at least one entry" if ssh_agent[:keys].nil? || ssh_agent[:keys].length < 1
123
+ ssh_agent[:containers].each do |container|
124
+ raise "Unknown container #{container}" if unpruned_config[:compose][container.to_sym].nil?
125
+ # environment:
126
+ # # make ssh keys available via ssh forwarding (see volume entry)
127
+ # - SSH_AUTH_SOCK=/ssh-agent/socket
128
+ #
129
+ # volumes_from:
130
+ # # Use configured whilp/ssh-agent long running container for keys
131
+ # - <project_name>-ssh-agent
132
+ compose[container.to_sym] ||= {}
133
+ compose[container.to_sym].deeper_merge! ({
134
+ environment: ['SSH_AUTH_SOCK=/root/.ssh/socket'],
135
+ volumes_from: [ssh_agent_name]
136
+ })
137
+ end
138
+ end
100
139
  end
101
140
  end
102
141
  end
@@ -3,7 +3,7 @@ class Docker::Container
3
3
  #FIXME: remove this method when pull #321 is accepted
4
4
  # Update the @info hash, which is the only mutable state in this object.
5
5
  def refresh!
6
- other = Docker::Container.all({all: true}, connection).find { |c|
6
+ other = Docker::Container.all({all: true}, connection).find { |c|
7
7
  c.id.start_with?(self.id) || self.id.start_with?(c.id)
8
8
  }
9
9
 
@@ -20,7 +20,15 @@ class Docker::Container
20
20
  end
21
21
 
22
22
  def name
23
- info['Names'][0].gsub(/^\//, '')
23
+ name = info['Names'][0] unless info['Names'].nil?
24
+ name = info['Name'] if name.nil? # straight docker containers appear to just use 'Name'
25
+
26
+ # puts "Name: #{info['Name']}"
27
+ # puts "Names: #{info['Names']}"
28
+ # puts "Names.nil?: #{info['Names'].nil?}"
29
+ # puts "Names.length: #{info['Names'].length}"
30
+
31
+ name.gsub(/^\//, '')
24
32
  end
25
33
 
26
34
  def up?
@@ -1,5 +1,5 @@
1
1
  module Docker
2
2
  module Rails
3
- VERSION = '0.6.0'
3
+ VERSION = '0.7.0'
4
4
  end
5
5
  end
@@ -3,7 +3,11 @@ require 'spec_helper'
3
3
  describe Docker::Rails::Config do
4
4
 
5
5
  subject(:options) { {} }
6
- subject(:config) { Docker::Rails::Config.new }
6
+ let(:target) { :foo }
7
+ let(:build) { 111 }
8
+ let(:dir_name) { 'rails' }
9
+ let(:project_name) { "#{dir_name}#{target}#{build}" }
10
+ subject(:config) { Docker::Rails::Config.new(build: build, target: target) }
7
11
 
8
12
  it 'should not raise error when key is not found' do
9
13
  config.clear
@@ -39,11 +43,11 @@ describe Docker::Rails::Config do
39
43
 
40
44
 
41
45
  context ':development' do
42
- let(:target_env){ :development}
46
+ let(:target) { :development }
43
47
  before(:each) {
44
48
  Dir.chdir(File.dirname(__FILE__)) do
45
49
  config.clear
46
- config.load!(target_env)
50
+ config.load!(target)
47
51
  end
48
52
  }
49
53
 
@@ -78,8 +82,8 @@ describe Docker::Rails::Config do
78
82
  }
79
83
 
80
84
  it 'web should have ssh-agent' do
81
- expect(compose_config[:web][:environment]).to include('SSH_AUTH_SOCK=/ssh-agent/socket')
82
- expect(compose_config[:web][:volumes_from]).to include('ssh-agent')
85
+ expect(compose_config[:web][:environment]).to include('SSH_AUTH_SOCK=/root/.ssh/socket')
86
+ expect(compose_config[:web][:volumes_from]).to include("#{project_name}_ssh_agent")
83
87
  end
84
88
  it 'web should have gemset' do
85
89
  expect(compose_config[:web][:environment]).to include('GEM_HOME=/gemset/2.2.2')
@@ -40,6 +40,7 @@ ssh-agent:
40
40
  # - ssh-agent
41
41
 
42
42
 
43
+ # shared extractions
43
44
  extractions: &extractions
44
45
  web:
45
46
  extract:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docker-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Ross
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-02 00:00:00.000000000 Z
11
+ date: 2015-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -160,6 +160,7 @@ files:
160
160
  - lib/docker/rails/cli/db_check.rb
161
161
  - lib/docker/rails/cli/gemset_volume.rb
162
162
  - lib/docker/rails/cli/main.rb
163
+ - lib/docker/rails/cli/ssh_agent.rb
163
164
  - lib/docker/rails/compose_config.rb
164
165
  - lib/docker/rails/config.rb
165
166
  - lib/docker/rails/ext/container.rb