capistrano_dockerbuild 1.0.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.
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :docker do
4
+
5
+ # Any extra command-line opts you'd like to insert into the `docker build` command
6
+ # Normally has no preset value (fastest option), but a popular full-rebuild-every-time (thorough build) selection is '--pull --no-cache --force-rm'
7
+ def docker_build_opts
8
+ fetch(:docker_build_opts, nil)
9
+ end
10
+
11
+ # Relative path to the Dockerfile for image building
12
+ def dockerfile
13
+ fetch(:dockerfile, 'Dockerfile')
14
+ end
15
+
16
+ # Docker build source path
17
+ def docker_build_context
18
+ fetch(:docker_build_context, '.')
19
+ end
20
+
21
+ def docker_cmd
22
+ fetch(:docker_cmd, 'docker')
23
+ end
24
+
25
+ def docker_build_image
26
+ (fetch(:docker_build_image) { fetch(:application) }) || raise('unable to discern docker_build_image name; specify :application or :docker_build_image')
27
+ end
28
+
29
+ def docker_build_tag
30
+ fetch(:docker_build_tag, 'latest')
31
+ end
32
+
33
+ def docker_build_image_tag
34
+ return fetch(:docker_build_image_tag) ||
35
+ set(:docker_build_image_tag, "#{docker_build_image}:#{docker_build_tag}")
36
+ end
37
+
38
+ def docker_build_image_latest_tag
39
+ return fetch(:docker_build_image_latest_tag) ||
40
+ set(:docker_build_image_latest_tag, "#{docker_build_image}:latest")
41
+ end
42
+
43
+ def docker_build_image_release_tag
44
+ return fetch(:docker_build_image_release_tag) ||
45
+ set(:docker_build_image_release_tag, "#{docker_build_image}:release-#{release_timestamp}")
46
+ end
47
+
48
+ def docker_build_image_revision_tag
49
+ return nil unless fetch(:current_revision)
50
+ return fetch(:docker_build_image_revision_tag) ||
51
+ set(:docker_build_image_revision_tag, "#{docker_build_image}:REVISION-#{fetch(:current_revision)}")
52
+ end
53
+
54
+ def docker_build_image_shortrev_tag
55
+ return nil unless fetch(:current_revision)
56
+ return fetch(:docker_build_image_shortrev_tag) ||
57
+ set(:docker_build_image_shortrev_tag, "#{docker_build_image}:rev-#{fetch(:current_revision)[0..6]}")
58
+ end
59
+
60
+ def docker_build_image_tags
61
+ [
62
+ docker_build_image_latest_tag,
63
+ docker_build_image_revision_tag,
64
+ docker_build_image_shortrev_tag,
65
+ docker_build_image_release_tag,
66
+ docker_build_image_tag,
67
+ ].uniq.compact
68
+ end
69
+
70
+ def docker_build_image_tags_opt
71
+ return docker_build_image_tags.map{|t| "--tag=#{t}"}.join(' ')
72
+ end
73
+
74
+ def docker_repo_url
75
+ fetch :docker_repo_url
76
+ end
77
+
78
+ def docker_repo_tag(local_tag)
79
+ return nil unless docker_repo_url
80
+ return "#{docker_repo_url}/#{local_tag}"
81
+ end
82
+
83
+ def docker_repo_tags
84
+ docker_build_image_tags.map{|t| docker_repo_tag(t)}
85
+ end
86
+
87
+ # TODO: remove this!!!!!!!!
88
+ task :debug do
89
+ byebug # rubocop:disable Lint/Debugger
90
+ puts 'pew pew!'
91
+ end
92
+
93
+ task :check_docker_build_role do
94
+ if roles(:docker_build).empty?
95
+ run_locally do
96
+ fatal ':docker_build role is required for remote builds'
97
+ raise 'missing :docker_build role'
98
+ end
99
+ end
100
+ unless roles(:docker_build).size == 1
101
+ run_locally do
102
+ fatal 'cannot assign :docker_build role to more than one server'
103
+ raise 'multiple :docker_build role assignments'
104
+ end
105
+ end
106
+ end
107
+
108
+ task :check_docker_build_root do
109
+ on roles(:docker_build).first do # |buildremote|
110
+ info "checking build path: #{build_path}"
111
+ execute :test, '-d', build_path # path exists
112
+ info 'checking for Dockerfile...'
113
+ execute :test, '-f', build_path.join(dockerfile) # Dockerfile is present
114
+ info "checking docker_build_context: #{docker_build_context}"
115
+ execute :test, '-d', build_path.join(docker_build_context) # Dockerfile is present
116
+ end
117
+ end
118
+
119
+ task :local_build_warning do
120
+ run_locally do
121
+ warn 'Building docker image directly from local workspace... (typically undesirable, skips several sanity checks)'
122
+ warn '>> assign :docker_build role to enable remote builds'
123
+ end
124
+ end
125
+
126
+
127
+ task :build_decision do
128
+ if roles(:docker_build).empty?
129
+ invoke 'docker:build_local'
130
+ else
131
+ invoke 'docker:build_remote'
132
+ end
133
+ end
134
+
135
+ task :local_build_deps => [:local_build_warning]
136
+
137
+ task :build_local => :local_build_deps do
138
+ current_build_dir = pwd
139
+ run_locally do
140
+ info "Current Working Directory: #{current_build_dir}"
141
+ execute docker_cmd,
142
+ 'build',
143
+ docker_build_opts,
144
+ "--file=#{dockerfile}",
145
+ docker_build_image_tags_opt,
146
+ docker_build_context
147
+ end
148
+ end
149
+
150
+ task :remote_build_deps => [:check_docker_build_role, :check_docker_build_root]
151
+
152
+ task :build_remote => :remote_build_deps do
153
+ on roles(:docker_build).first do |buildremote|
154
+ info "Building docker image on :docker_build role: #{buildremote}"
155
+ within build_path do
156
+ execute docker_cmd, :ps
157
+ execute docker_cmd,
158
+ 'build',
159
+ docker_build_opts,
160
+ "--file=#{dockerfile}",
161
+ docker_build_image_tags_opt,
162
+ docker_build_context
163
+ end
164
+ end
165
+ end
166
+
167
+ task :tag do
168
+ if roles(:docker_build).empty?
169
+ invoke 'docker:tag_local'
170
+ else
171
+ invoke 'docker:tag_remote'
172
+ end
173
+ end
174
+
175
+ task :tag_local do
176
+ if docker_build_image_release_tag
177
+ run_locally do
178
+ info 'Re-tagging docker images for upstream push...'
179
+ docker_repo_tags.each do |repo_tag|
180
+ execute docker_cmd, :tag,
181
+ docker_build_image_latest_tag,
182
+ repo_tag
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ task :tag_remote => :check_docker_build_role do
189
+ if docker_build_image_release_tag
190
+ on roles(:docker_build).first do # |buildremote|
191
+ info 'Re-tagging docker images for upstream push...'
192
+ docker_repo_tags.each do |repo_tag|
193
+ execute docker_cmd, :tag,
194
+ docker_build_image_latest_tag,
195
+ repo_tag
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ desc "Push the 'latest' image to the repository"
202
+ task :push do
203
+ if roles(:docker_build).empty?
204
+ invoke 'docker:push_local'
205
+ else
206
+ invoke 'docker:push_remote'
207
+ end
208
+ end
209
+
210
+ task :push_local => :tag_local do
211
+ run_locally do
212
+ info 'Pushing docker images upstream...'
213
+ docker_repo_tags.each do |repo_tag|
214
+ execute docker_cmd, :push,
215
+ repo_tag
216
+ end
217
+ end
218
+ end
219
+
220
+ task :push_remote => [:check_docker_build_role, :tag_remote] do
221
+ on roles(:docker_build).first do # |buildremote|
222
+ info 'Pushing docker images upstream...'
223
+ docker_repo_tags.each do |repo_tag|
224
+ execute docker_cmd, :push,
225
+ repo_tag
226
+ end
227
+ end
228
+ end
229
+
230
+ desc 'Build the docker container image'
231
+ task :build do
232
+ invoke 'docker:build_decision'
233
+ end
234
+
235
+ desc 'Build and then push docker container image'
236
+ task :build_push do
237
+ invoke 'docker:build'
238
+ invoke 'docker:push'
239
+ end
240
+
241
+ # Default `cap env deploy` flow hook
242
+ task :capdeploy_hook do
243
+ if fetch(:dockerbuild_deployhook, true)
244
+ invoke 'docker:build_push'
245
+ end
246
+ end
247
+
248
+ task :trim_release_roles do
249
+ if fetch(:dockerbuild_trim_release_roles, true)
250
+ docker_build_server = [roles(:docker_build).first]
251
+ (release_roles(:all) - docker_build_server).each do |server|
252
+ server.set :no_release, true
253
+ end
254
+ end
255
+ end
256
+
257
+ end # namespace :docker do
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # add the build->push process onto the end of the deploy:published stack
4
+ after 'deploy:published', 'docker:capdeploy_hook'
5
+
6
+ # push the no_release auto-cull to the very front of the task stack
7
+ before 'deploy:starting', 'docker:trim_release_roles'
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'byebug'
4
+ require 'tempfile'
5
+
6
+ namespace :docker do
7
+ namespace :compose do
8
+
9
+ def docker_compose_cmd
10
+ fetch(:docker_compose_cmd, 'docker-compose')
11
+ end
12
+
13
+ def docker_compose_project
14
+ (fetch(:docker_compose_project) { fetch(:application) }) || raise('unable to discern docker_compose_project name; specify :application or :docker_compose_project')
15
+ end
16
+
17
+ def docker_compose_opts
18
+ fetch(:docker_compose_opts, nil)
19
+ end
20
+
21
+ def docker_compose_up_opts
22
+ fetch(:docker_compose_up_opts, '--no-build --remove-orphans')
23
+ end
24
+
25
+ def docker_compose_stop_opts
26
+ fetch(:docker_compose_stop_opts, nil)
27
+ end
28
+
29
+ def docker_compose_down_opts
30
+ fetch(:docker_compose_down_opts, '-v --remove-orphans')
31
+ end
32
+
33
+ # Relative path to the docker-compose.yml for deployments
34
+ def docker_compose_file
35
+ fetch(:docker_compose_file, 'docker-compose.yml')
36
+ end
37
+
38
+ # Path for docker-compose remote deployments
39
+ def docker_compose_path
40
+ fetch(:docker_compose_path) { build_path }
41
+ end
42
+
43
+
44
+ task :check_docker_compose_root do
45
+ on roles(:docker_compose) do |remote|
46
+ # info "ON HOST #{remote}"
47
+ # info "checking docker_compose_path: #{docker_compose_path}"
48
+ # should really fix this to preserve typical capistrano dir-tree, but meh for now...
49
+ unless test("[ -d #{docker_compose_path} ]")
50
+ warn "docker_compose_path does not exist on host (#{remote}): #{docker_compose_path}"
51
+ warn "creating docker_compose_path on host (#{remote}): #{docker_compose_path}"
52
+ warn 'this can break future `cap stage deploy` execution on build hosts'
53
+ execute :mkdir, '-p', docker_compose_path
54
+ end
55
+ end
56
+ end
57
+
58
+ task :local_source_warning do
59
+ run_locally do
60
+ warn 'Using docker-compose files directly from local workspace... (typically undesirable)'
61
+ warn '>> assign :docker_build role to enable remote builds'
62
+ end
63
+ end
64
+
65
+
66
+ desc 'copies a new docker_compose_file from the docker_build role to each docker_compose role'
67
+ task :upload do
68
+ if roles(:docker_compose).empty?
69
+ invoke 'docker:compose:upload_local'
70
+ else
71
+ invoke 'docker:compose:upload_remote'
72
+ end
73
+ end
74
+
75
+ # copies a new docker-compose.yml file up to each docker_compose host
76
+ task :upload_local do
77
+ run_locally do
78
+ info 'Nothing to do here, docker-compose will run locally...'
79
+ end
80
+ end
81
+
82
+ # copies a new docker-compose.yml file up to each docker_compose host
83
+ task :upload_remote => :check_docker_compose_root do
84
+ tmp_file = Tempfile.new('upload_remote')
85
+ tmp_file_path = tmp_file.path
86
+ tmp_file.close(false)
87
+ begin
88
+ docker_build_role = roles(:docker_build).first
89
+ upload_roles = roles(:docker_compose) - [docker_build_role]
90
+ unless upload_roles.empty?
91
+ if docker_build_role
92
+ on docker_build_role do |build_remote|
93
+ info "fetching #{build_remote}:#{docker_compose_path.join(docker_compose_file)}"
94
+ download! docker_compose_path.join(docker_compose_file), tmp_file_path
95
+ end
96
+ else
97
+ # byebug
98
+ invoke 'docker:compose:local_source_warning'
99
+ run_locally do
100
+ warn "fetching #{docker_compose_file}"
101
+ download! docker_compose_file, tmp_file_path
102
+ end
103
+ end
104
+ on upload_roles do |upload_role|
105
+ info "uploading to #{upload_role}:#{docker_compose_path.join(docker_compose_file)}"
106
+ upload! tmp_file_path, docker_compose_path.join(docker_compose_file)
107
+ end
108
+ end
109
+ ensure
110
+ File.unlink(tmp_file_path)
111
+ end
112
+ end
113
+
114
+
115
+ desc 'list current services state'
116
+ task :ps do
117
+ if roles(:docker_compose).empty?
118
+ invoke 'docker:compose:ps_local'
119
+ else
120
+ invoke 'docker:compose:ps_remote'
121
+ end
122
+ end
123
+
124
+ task :ps_local do
125
+ current_build_dir = pwd
126
+ run_locally do
127
+ info "Current Working Directory: #{current_build_dir}"
128
+ execute docker_compose_cmd,
129
+ "--file=#{docker_compose_file}",
130
+ "--project-name=#{docker_compose_project}",
131
+ docker_compose_opts,
132
+ :ps
133
+ end
134
+ end
135
+
136
+ task :ps_remote do
137
+ on roles(:docker_compose) do |buildremote|
138
+ info "ON HOST #{buildremote}"
139
+ within build_path do
140
+ execute docker_compose_cmd,
141
+ "--file=#{docker_compose_file}",
142
+ "--project-name=#{docker_compose_project}",
143
+ docker_compose_opts,
144
+ :ps
145
+ end
146
+ end
147
+ end
148
+
149
+
150
+ desc 'starts/updates services'
151
+ task :up do
152
+ if roles(:docker_compose).empty?
153
+ invoke 'docker:compose:up_local'
154
+ else
155
+ invoke 'docker:compose:up_remote'
156
+ end
157
+ end
158
+
159
+ task :up_local do
160
+ current_build_dir = pwd
161
+ run_locally do
162
+ info "Current Working Directory: #{current_build_dir}"
163
+ execute docker_compose_cmd,
164
+ "--file=#{docker_compose_file}",
165
+ "--project-name=#{docker_compose_project}",
166
+ docker_compose_opts,
167
+ :up,
168
+ docker_compose_up_opts
169
+ end
170
+ end
171
+
172
+ task :up_remote do
173
+ on roles(:docker_compose) do |buildremote|
174
+ info "ON HOST #{buildremote}"
175
+ within build_path do
176
+ execute docker_compose_cmd,
177
+ "--file=#{docker_compose_file}",
178
+ "--project-name=#{docker_compose_project}",
179
+ docker_compose_opts,
180
+ :up, '-d',
181
+ docker_compose_up_opts
182
+ end
183
+ end
184
+ end
185
+
186
+
187
+ desc 'stops services but leaves them created'
188
+ task :stop do
189
+ if roles(:docker_compose).empty?
190
+ invoke 'docker:compose:stop_local'
191
+ else
192
+ invoke 'docker:compose:stop_remote'
193
+ end
194
+ end
195
+
196
+ task :stop_local do
197
+ current_build_dir = pwd
198
+ run_locally do
199
+ info "Current Working Directory: #{current_build_dir}"
200
+ execute docker_compose_cmd,
201
+ "--file=#{docker_compose_file}",
202
+ "--project-name=#{docker_compose_project}",
203
+ docker_compose_opts,
204
+ :stop,
205
+ docker_compose_stop_opts
206
+ end
207
+ end
208
+
209
+ task :stop_remote do
210
+ on roles(:docker_compose) do |buildremote|
211
+ info "ON HOST #{buildremote}"
212
+ within build_path do
213
+ execute docker_compose_cmd,
214
+ "--file=#{docker_compose_file}",
215
+ "--project-name=#{docker_compose_project}",
216
+ docker_compose_opts,
217
+ :stop,
218
+ docker_compose_stop_opts
219
+ end
220
+ end
221
+ end
222
+
223
+
224
+ desc 'stop and clean up all services'
225
+ task :down do
226
+ if roles(:docker_compose).empty?
227
+ invoke 'docker:compose:down_local'
228
+ else
229
+ invoke 'docker:compose:down_remote'
230
+ end
231
+ end
232
+
233
+ task :down_local do
234
+ current_build_dir = pwd
235
+ run_locally do
236
+ info "Current Working Directory: #{current_build_dir}"
237
+ execute docker_compose_cmd,
238
+ "--file=#{docker_compose_file}",
239
+ "--project-name=#{docker_compose_project}",
240
+ docker_compose_opts,
241
+ :down,
242
+ docker_compose_down_opts
243
+ end
244
+ end
245
+
246
+ task :down_remote do
247
+ on roles(:docker_compose) do |buildremote|
248
+ info "ON HOST #{buildremote}"
249
+ within build_path do
250
+ execute docker_compose_cmd,
251
+ "--file=#{docker_compose_file}",
252
+ "--project-name=#{docker_compose_project}",
253
+ docker_compose_opts,
254
+ :down,
255
+ docker_compose_down_opts
256
+ end
257
+ end
258
+ end
259
+
260
+
261
+ desc 'Update docker-compose on all hosts and re-up'
262
+ task :update do
263
+ invoke 'docker:compose:upload'
264
+ invoke 'docker:compose:up'
265
+ end
266
+
267
+ desc '(alias for docker:compose:update)'
268
+ task :deploy => :update
269
+
270
+ # Default `cap env deploy` flow hook
271
+ task :capdeploy_hook do
272
+ if fetch(:dockercompose_deployhook, true)
273
+ invoke 'docker:compose:update'
274
+ end
275
+ end
276
+
277
+ end # namespace :compose do
278
+ end # namespace :docker do
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # add the build->push process onto the end of the deploy:published stack
4
+ after 'deploy:published', 'docker:compose:capdeploy_hook'