docker-rails 0.6.0 → 0.7.0

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