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 +4 -4
- data/README.md +65 -40
- data/lib/docker/rails.rb +1 -0
- data/lib/docker/rails/app.rb +159 -104
- data/lib/docker/rails/cli/gemset_volume.rb +6 -7
- data/lib/docker/rails/cli/main.rb +9 -2
- data/lib/docker/rails/cli/ssh_agent.rb +17 -0
- data/lib/docker/rails/config.rb +82 -43
- data/lib/docker/rails/ext/container.rb +10 -2
- data/lib/docker/rails/version.rb +1 -1
- data/spec/docker/rails/config_spec.rb +9 -5
- data/spec/docker/rails/docker-rails.yml +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ee7498a73d43a8cc24e329c9bdf688e5bde379f
|
4
|
+
data.tar.gz: 817e09489010ddcc26b6783335ee5d88281e016d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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. `
|
33
|
-
5. `
|
34
|
-
6. `
|
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.
|
76
|
-
docker-rails build <target> # Build for the given build/target e.g.
|
77
|
-
docker-rails ci <target> # Execute the works, everything with cleanup included e.g.
|
78
|
-
docker-rails cleanup <target> # Runs container cleanup functions stop, rm_volumes, rm_compose, rm_dangling, ps_all e.g.
|
79
|
-
docker-rails compose <target> # Writes a resolved docker-compose.yml file e.g.
|
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.
|
82
|
-
docker-rails gemset_volume <command>
|
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.
|
85
|
-
docker-rails ps_all # List all remaining containers regardless of state e.g.
|
86
|
-
docker-rails rm_dangling # Remove danging images e.g.
|
87
|
-
docker-rails rm_volumes <target> # Stop all running containers and remove corresponding volumes for the given build/target e.g.
|
88
|
-
docker-rails
|
89
|
-
docker-rails
|
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
|
-
#
|
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
|
data/lib/docker/rails.rb
CHANGED
data/lib/docker/rails/app.rb
CHANGED
@@ -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'] =
|
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!(
|
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
|
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
|
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
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
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
|
-
|
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
|
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 #{
|
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
|
232
|
+
def create_gemset_volume
|
282
233
|
begin
|
283
|
-
Docker::Container.get(
|
284
|
-
puts "Gem data volume container #{
|
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 #{
|
288
|
-
puts "Gem data volume container #{
|
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 #{
|
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
|
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).
|
7
|
+
App.configured(target, options).create_gemset_volume
|
11
8
|
end
|
12
9
|
|
13
|
-
|
14
|
-
|
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.
|
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).
|
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
|
data/lib/docker/rails/config.rb
CHANGED
@@ -3,29 +3,46 @@ module Docker
|
|
3
3
|
require 'dry/config'
|
4
4
|
class Config < Dry::Config::Base
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
42
|
-
raise "Unknown target environment '#{environment.to_sym}'" if
|
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
|
-
|
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
|
-
|
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
|
-
|
70
|
-
|
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
|
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
|
93
|
-
|
94
|
-
|
118
|
+
def generate_ssh_agent(compose, unpruned_config)
|
119
|
+
ssh_agent = unpruned_config[:'ssh-agent']
|
120
|
+
if !ssh_agent.nil?
|
95
121
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
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]
|
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?
|
data/lib/docker/rails/version.rb
CHANGED
@@ -3,7 +3,11 @@ require 'spec_helper'
|
|
3
3
|
describe Docker::Rails::Config do
|
4
4
|
|
5
5
|
subject(:options) { {} }
|
6
|
-
|
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(:
|
46
|
+
let(:target) { :development }
|
43
47
|
before(:each) {
|
44
48
|
Dir.chdir(File.dirname(__FILE__)) do
|
45
49
|
config.clear
|
46
|
-
config.load!(
|
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
|
82
|
-
expect(compose_config[:web][:volumes_from]).to include(
|
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')
|
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.
|
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-
|
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
|