capistrano-nomad 0.6.4 → 0.6.5

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
  SHA256:
3
- metadata.gz: 707ded6b01d5903708a362ef6e98f3c56d5e52fd1557bfad47f1844cb7d6c96e
4
- data.tar.gz: 68ea61e7025400e99c97683b8f3390357a0f04cf5b173846bba378d8bdd5fa3d
3
+ metadata.gz: 659b43a201195b9cefc001ff096e0edc07967303695f19466e612061028f81d7
4
+ data.tar.gz: 236271b31a3cae45ba09828afb8cc27854eee2ce2329166dfde818683610c193
5
5
  SHA512:
6
- metadata.gz: 19a5ee3bcddf663ed3820245f10d0c35dee3cf2d1659b44f61947696c7ea24c263696c3c25d6dba032d14293706f0b9b68ee3e9cdbd41b66309e43eb92a0e297
7
- data.tar.gz: b20019a85564cc4eee312bfaa17934becab4b73c2505c7a0b9511b1759b320c56fda3656b7f2b80b3cd090e347f05f5c0a2ef3e283a078850636c37ea58f20fb
6
+ metadata.gz: bce586af52de2b0d1d679bb7dcbb7b1d8ccf9fb23bfa8296ff9fb2cff379deb8a35071b75164e7750918f7c1d52364340beb40877fbacd41cbd70bb758af1e40
7
+ data.tar.gz: ace5161204cdc20b739a462971ba9a2d18d194205ecc4ef2beb68e7820da0e4a02e9b711317babd5e76bf368892b358b9c8f2abdcd82dcca92303faa79bcc77a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capistrano-nomad (0.6.4)
4
+ capistrano-nomad (0.6.5)
5
5
  activesupport (<= 7.0.8)
6
6
  byebug
7
7
  capistrano (~> 3.0)
data/README.md CHANGED
@@ -37,8 +37,8 @@ Within `deploy.rb`
37
37
  set :nomad_jobs_path, "nomad/jobs"
38
38
  set :nomad_var_files_path, "nomad/vars"
39
39
 
40
- # Accessible in all .erb files
41
- set :nomad_erb_vars, (lambda do
40
+ # Make variables available to all template .erb files
41
+ set :nomad_template_vars, (lambda do
42
42
  {
43
43
  env_name: fetch(:stage).to_sym,
44
44
  domain: fetch(:domain),
@@ -46,6 +46,15 @@ set :nomad_erb_vars, (lambda do
46
46
  }
47
47
  end)
48
48
 
49
+ # Make helpers available to all template .erb files
50
+ nomad_template_helpers do
51
+ def restart_stanza
52
+ <<-EOF
53
+
54
+ EOF
55
+ end
56
+ end
57
+
49
58
  # Docker image types
50
59
  nomad_docker_image_type :backend,
51
60
  path: "local/path/backend",
@@ -55,10 +64,13 @@ nomad_docker_image_type :backend,
55
64
  nomad_docker_image_type :redis,
56
65
  path: "/absolute/path/redis",
57
66
  alias: "gcr.io/axsuul/redis"
67
+ nomad_docker_image_type :postgres,
68
+ alias_digest: "postgres:5.0.0"
58
69
 
59
70
  # Jobs
60
- nomad_job :frontend
61
71
  nomad_job :backend, docker_image_types: [:backend], var_files: [:rails]
72
+ nomad_job :frontend
73
+ nomad_job :postgres, docker_image_types: [:postgres]
62
74
  nomad_job :redis, docker_image_types: [:redis]
63
75
  nomad_job :"traefik-default", template: :traefik, erb_vars: { role: :default }
64
76
  nomad_job :"traefik-secondary", template: :traefik, erb_vars: { role: :secondary }
@@ -74,14 +86,22 @@ Deploy all with
74
86
  cap production nomad:all:deploy
75
87
  ```
76
88
 
77
- Deploy individually with
89
+ Deploy jobs individually with
78
90
 
79
91
  ```shell
80
92
  cap production nomad:app:deploy
81
- cap production nomad:redis:purge
82
93
  cap production nomad:analytics:grafana:deploy
83
94
  ```
84
95
 
96
+ Manage jobs with
97
+
98
+ ```shell
99
+ cap production nomad:app:stop
100
+ cap production nomad:redis:purge
101
+ cap production nomad:analytics:grafana:restart
102
+ cap production nomad:postgres:status
103
+ ```
104
+
85
105
  Open console with
86
106
 
87
107
  ```shell
@@ -90,10 +110,19 @@ cap production nomad:app:console TASK=custom-task-name
90
110
  cap production nomad:analytics:grafana:console
91
111
  ```
92
112
 
113
+ Show logs with
114
+
115
+ ```shell
116
+ cap production nomad:app:logs
117
+ cap production nomad:app:stdout
118
+ cap production nomad:app:stderr
119
+ cap production nomad:analytics:grafana:follow
120
+ ```
121
+
93
122
  Create missing and delete unused namespaces
94
123
 
95
124
  ```shell
96
- cap production nomad:all:replace_namespaces
125
+ cap production nomad:all:modify_namespaces
97
126
  ```
98
127
 
99
128
  ## Development
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "capistrano-nomad"
5
- spec.version = "0.6.4"
5
+ spec.version = "0.6.5"
6
6
  spec.authors = ["James Hu"]
7
7
 
8
8
  spec.summary = "Capistrano plugin for deploying and managing Nomad jobs"
@@ -70,6 +70,9 @@ def capistrano_nomad_build_docker_image_for_type(image_type)
70
70
 
71
71
  return unless attributes
72
72
 
73
+ # No need to build if there's no path
74
+ return unless attributes[:path]
75
+
73
76
  args = [
74
77
  # Ensure images are built for x86_64 which is production env otherwise it will default to local development env
75
78
  # which can be arm64 (Apple Silicon)
@@ -106,26 +109,30 @@ def capistrano_nomad_build_docker_image_for_type(image_type)
106
109
  end
107
110
 
108
111
  def capistrano_nomad_push_docker_image_for_type(image_type, is_manifest_updated: true)
109
- # Allows end user to add hooks
110
- invoke("nomad:docker_images:push")
112
+ attributes = fetch(:nomad_docker_image_types)[image_type]
113
+ alias_digest = attributes&.dig(:alias_digest)
111
114
 
112
115
  run_locally do
113
- interaction_handler = CapistranoNomadDockerPushImageInteractionHandler.new
114
- image_alias = capistrano_nomad_build_docker_image_alias(image_type)
116
+ # Don't push Docker image if alias digest is already passed in
117
+ unless alias_digest
118
+ interaction_handler = CapistranoNomadDockerPushImageInteractionHandler.new
119
+ image_alias = capistrano_nomad_build_docker_image_alias(image_type)
120
+
121
+ # We should not proceed if image cannot be pushed
122
+ unless execute("docker push #{image_alias}", interaction_handler: interaction_handler)
123
+ raise StandardError, "Docker image push unsuccessful!"
124
+ end
115
125
 
116
- # We should not proceed if image cannot be pushed
117
- unless execute("docker push #{image_alias}", interaction_handler: interaction_handler)
118
- raise StandardError, "Docker image push unsuccessful!"
119
- end
126
+ return unless is_manifest_updated
120
127
 
121
- return unless is_manifest_updated
128
+ # Has the @sha256:xxxx appended so we have the ability to also target by digest
129
+ alias_digest = "#{image_alias}@#{interaction_handler.last_digest}"
130
+ end
122
131
 
123
132
  # Update image type manifest
124
133
  capistrano_nomad_update_docker_image_types_manifest(image_type,
125
134
  alias: image_alias,
126
-
127
- # Has the @sha256:xxxx appended so we have the ability to also target by digest
128
- alias_digest: "#{image_alias}@#{interaction_handler.last_digest}",
135
+ alias_digest: alias_digest,
129
136
  )
130
137
  end
131
138
  end
@@ -50,12 +50,12 @@ def nomad_job(name, attributes = {})
50
50
 
51
51
  desc "Run #{description_name} job"
52
52
  task :run do
53
- capistrano_nomad_run_jobs([name], namespace: namespace)
53
+ capistrano_nomad_run_jobs([name], namespace: namespace, is_detached: false)
54
54
  end
55
55
 
56
56
  desc "Purge and run #{description_name} job again"
57
57
  task :rerun do
58
- capistrano_nomad_rerun_jobs([name], namespace: namespace)
58
+ capistrano_nomad_rerun_jobs([name], namespace: namespace, is_detached: false)
59
59
  end
60
60
 
61
61
  desc "Upload and plan #{description_name} job"
@@ -65,22 +65,17 @@ def nomad_job(name, attributes = {})
65
65
 
66
66
  desc "Upload and run #{description_name} job"
67
67
  task :upload_run do
68
- capistrano_nomad_upload_run_jobs([name], namespace: namespace)
68
+ capistrano_nomad_upload_run_jobs([name], namespace: namespace, is_detached: false)
69
69
  end
70
70
 
71
71
  desc "Upload and re-run #{description_name} job"
72
72
  task :upload_rerun do
73
- capistrano_nomad_upload_rerun_jobs([name], namespace: namespace)
73
+ capistrano_nomad_upload_rerun_jobs([name], namespace: namespace, is_detached: false)
74
74
  end
75
75
 
76
76
  desc "Deploy #{description_name} job"
77
77
  task :deploy do
78
- capistrano_nomad_deploy_jobs([name], namespace: namespace)
79
- end
80
-
81
- desc "Restart #{description_name} job"
82
- task :restart do
83
- capistrano_nomad_restart_jobs([name], namespace: namespace)
78
+ capistrano_nomad_deploy_jobs([name], namespace: namespace, is_detached: false)
84
79
  end
85
80
 
86
81
  desc "Stop #{description_name} job"
@@ -88,6 +83,11 @@ def nomad_job(name, attributes = {})
88
83
  capistrano_nomad_stop_jobs([name], namespace: namespace)
89
84
  end
90
85
 
86
+ desc "Restart #{description_name} job"
87
+ task :restart do
88
+ capistrano_nomad_restart_jobs([name], namespace: namespace)
89
+ end
90
+
91
91
  desc "Purge #{description_name} job"
92
92
  task :purge do
93
93
  capistrano_nomad_purge_jobs([name], namespace: namespace, is_detached: false)
@@ -98,9 +98,32 @@ def nomad_job(name, attributes = {})
98
98
  capistrano_nomad_display_job_status(name, namespace: namespace)
99
99
  end
100
100
 
101
- desc "Open console to #{description_name} job. Specify task by passing TASK environment variable"
101
+ desc "Open console to #{description_name} job. Specify task with TASK, command with CMD"
102
102
  task :console do
103
- capistrano_nomad_exec_within_job(name, "/bin/bash", namespace: namespace, task: ENV["TASK"].presence)
103
+ command = ENV["CMD"].presence || "/bin/bash"
104
+
105
+ capistrano_nomad_exec_within_job(name, command, namespace: namespace, task: ENV["TASK"])
106
+ end
107
+
108
+ desc "Display stdout and stderr of #{description_name} job. Specify task with TASK"
109
+ task :logs do
110
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stdout: true)
111
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stderr: true)
112
+ end
113
+
114
+ desc "Display stdout of #{description_name} job. Specify task with TASK"
115
+ task :stdout do
116
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stdout: true)
117
+ end
118
+
119
+ desc "Display stderr of #{description_name} job. Specify task with TASK"
120
+ task :stderr do
121
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stderr: true)
122
+ end
123
+
124
+ desc "Follow logs of #{description_name} job. Specify task with TASK"
125
+ task :follow do
126
+ capistrano_nomad_display_job_logs(name, namespace: namespace, f: true)
104
127
  end
105
128
  end
106
129
  end
@@ -116,3 +139,7 @@ def nomad_job(name, attributes = {})
116
139
  end
117
140
  end
118
141
  end
142
+
143
+ def nomad_template_helpers(&block)
144
+ CapistranoNomadErbNamespace.class_eval(&block)
145
+ end
@@ -10,16 +10,6 @@ class CapistranoNomadErbNamespace
10
10
  end
11
11
  end
12
12
 
13
- # Use default value passed in unless `nomad_job_task_cpu_resource` is set
14
- def build_nomad_job_task_cpu_resource(default:)
15
- nomad_job_task_cpu_resource == "null" ? default : nomad_job_task_cpu_resource
16
- end
17
-
18
- # Use default value passed in unless `nomad_job_task_memory_resource` is set
19
- def build_nomad_job_task_memory_resource(default:)
20
- nomad_job_task_memory_resource == "null" ? default : nomad_job_task_memory_resource
21
- end
22
-
23
13
  # rubocop:disable Style/MissingRespondToMissing
24
14
  def method_missing(name, *args)
25
15
  instance_variable = "@#{name}"
@@ -123,25 +113,52 @@ def capistrano_nomad_capture_nomad_command(*args)
123
113
  output
124
114
  end
125
115
 
126
- def capistrano_nomad_exec_within_job(name, command, namespace: nil, task: nil)
127
- on(roles(:manager)) do
128
- task ||= name
116
+ def capistrano_nomad_find_job_task_details(name, namespace: nil, task: nil)
117
+ task = task.presence || name
129
118
 
130
- # Find alloc id that contains task
131
- output = capistrano_nomad_capture_nomad_command(
132
- :job,
133
- :allocs,
134
- { namespace: namespace, t: "'{{range .}}{{ .ID }},{{ .TaskGroup }}|{{end}}'" },
135
- name,
136
- )
137
- alloc_id = output.split("|").map { |s| s.split(",") }.find { |_, t| t == task.to_s }&.first
119
+ # Find alloc id that contains task
120
+ allocs_output = capistrano_nomad_capture_nomad_command(
121
+ :job,
122
+ :allocs,
123
+ { namespace: namespace, t: "'{{range .}}{{ .ID }},{{ .TaskGroup }}|{{end}}'" },
124
+ name,
125
+ )
126
+ alloc_id = allocs_output.split("|").map { |s| s.split(",") }.find { |_, t| t == task.to_s }&.first
127
+
128
+ # Can't continue if we can't choose an alloc id
129
+ return unless alloc_id
130
+
131
+ tasks_output = capistrano_nomad_capture_nomad_command(
132
+ :alloc,
133
+ :status,
134
+ { namespace: namespace, t: "'{{range $key, $value := .TaskStates}}{{ $key }},{{ .State }}|{{end}}'" },
135
+ alloc_id,
136
+ )
137
+ tasks_by_score = tasks_output.split("|").each_with_object({}) do |task_output, hash|
138
+ task, state = task_output.split(",")
139
+
140
+ score = 0
141
+ score += 5 if state == "running"
142
+ score += 5 unless task.match?(/connect-proxy/)
143
+
144
+ hash[task] = score
145
+ end
146
+ task = tasks_by_score.max_by { |_, v| v }.first
147
+
148
+ {
149
+ alloc_id: alloc_id,
150
+ name: task,
151
+ }
152
+ end
138
153
 
139
- if alloc_id
154
+ def capistrano_nomad_exec_within_job(name, command, namespace: nil, task: nil)
155
+ on(roles(:manager)) do
156
+ if (task_details = capistrano_nomad_find_job_task_details(name, namespace: namespace, task: task))
140
157
  capistrano_nomad_execute_nomad_command(
141
158
  :alloc,
142
159
  :exec,
143
- { namespace: namespace, task: task },
144
- alloc_id,
160
+ { namespace: namespace, task: task_details[:name] },
161
+ task_details[:alloc_id],
145
162
  command,
146
163
  )
147
164
  else
@@ -175,7 +192,7 @@ def capistrano_nomad_upload_file(local_path:, remote_path:, erb_vars: {})
175
192
  }
176
193
 
177
194
  # Add global ERB vars
178
- final_erb_vars.merge!(fetch(:nomad_erb_vars) || {})
195
+ final_erb_vars.merge!(fetch(:nomad_template_vars) || {})
179
196
 
180
197
  # Add job-specific ERB vars
181
198
  final_erb_vars.merge!(erb_vars)
@@ -269,11 +286,11 @@ def capistrano_nomad_plan_jobs(names, *args)
269
286
  end
270
287
  end
271
288
 
272
- def capistrano_nomad_run_jobs(names, namespace: nil)
289
+ def capistrano_nomad_run_jobs(names, namespace: nil, is_detached: true)
273
290
  names.each do |name|
274
291
  run_options = {
275
292
  namespace: namespace,
276
- detach: true,
293
+ detach: is_detached,
277
294
 
278
295
  # Don't reset counts since they may have been scaled
279
296
  preserve_counts: true,
@@ -292,44 +309,52 @@ def capistrano_nomad_run_jobs(names, namespace: nil)
292
309
  end
293
310
 
294
311
  # Remove job and run again
295
- def capistrano_nomad_rerun_jobs(names)
312
+ def capistrano_nomad_rerun_jobs(names, **options)
313
+ general_options = options.slice!(:is_detached)
314
+
296
315
  names.each do |name|
297
316
  # Wait for jobs to be purged before running again
298
- capistrano_nomad_purge_jobs([name], is_detached: false)
317
+ capistrano_nomad_purge_jobs([name], **general_options.merge(is_detached: false))
299
318
 
300
- capistrano_nomad_run_jobs([name])
319
+ capistrano_nomad_run_jobs([name], **general_options.merge(options))
301
320
  end
302
321
  end
303
322
 
304
- def capistrano_nomad_upload_plan_jobs(names, *args)
305
- capistrano_nomad_upload_jobs(names, *args)
306
- capistrano_nomad_plan_jobs(names, *args)
323
+ def capistrano_nomad_upload_plan_jobs(names, **options)
324
+ capistrano_nomad_upload_jobs(names, **options)
325
+ capistrano_nomad_plan_jobs(names, **options)
307
326
  end
308
327
 
309
- def capistrano_nomad_upload_run_jobs(names, *args)
310
- capistrano_nomad_upload_jobs(names, *args)
311
- capistrano_nomad_run_jobs(names, *args)
328
+ def capistrano_nomad_upload_run_jobs(names, **options)
329
+ general_options = options.slice!(:is_detached)
330
+
331
+ capistrano_nomad_upload_jobs(names, **general_options)
332
+ capistrano_nomad_run_jobs(names, **general_options.merge(options))
312
333
  end
313
334
 
314
- def capistrano_nomad_upload_rerun_jobs(names, *args)
315
- capistrano_nomad_upload_jobs(names, *args)
316
- capistrano_nomad_rerun_jobs(names, *args)
335
+ def capistrano_nomad_upload_rerun_jobs(names, **options)
336
+ general_options = options.slice!(:is_detached)
337
+
338
+ capistrano_nomad_upload_jobs(names, **general_options)
339
+ capistrano_nomad_rerun_jobs(names, **general_options.merge(options))
317
340
  end
318
341
 
319
- def capistrano_nomad_deploy_jobs(names, *args)
320
- capistrano_nomad_assemble_jobs_docker_images(names, *args)
321
- capistrano_nomad_upload_run_jobs(names, *args)
342
+ def capistrano_nomad_deploy_jobs(names, **options)
343
+ general_options = options.slice!(:is_detached)
344
+
345
+ capistrano_nomad_assemble_jobs_docker_images(names, **general_options)
346
+ capistrano_nomad_upload_run_jobs(names, **general_options.merge(options))
322
347
  end
323
348
 
324
- def capistrano_nomad_stop_jobs(names, namespace: nil)
349
+ def capistrano_nomad_stop_jobs(names, **options)
325
350
  names.each do |name|
326
- capistrano_nomad_execute_nomad_command(:job, :stop, { namespace: namespace }, name)
351
+ capistrano_nomad_execute_nomad_command(:job, :stop, options, name)
327
352
  end
328
353
  end
329
354
 
330
- def capistrano_nomad_restart_jobs(names, namespace: nil)
355
+ def capistrano_nomad_restart_jobs(names, **options)
331
356
  names.each do |name|
332
- capistrano_nomad_execute_nomad_command(:job, :restart, { namespace: namespace }, name)
357
+ capistrano_nomad_execute_nomad_command(:job, :restart, options, name)
333
358
  end
334
359
  end
335
360
 
@@ -339,6 +364,29 @@ def capistrano_nomad_purge_jobs(names, namespace: nil, is_detached: true)
339
364
  end
340
365
  end
341
366
 
342
- def capistrano_nomad_display_job_status(name, *args)
343
- capistrano_nomad_execute_nomad_command(:status, *args, name)
367
+ def capistrano_nomad_display_job_status(name, **options)
368
+ capistrano_nomad_execute_nomad_command(:status, options, name)
369
+ end
370
+
371
+ def capistrano_nomad_display_job_logs(name, namespace: nil, **options)
372
+ if (task_details = capistrano_nomad_find_job_task_details(name, namespace: namespace, task: ENV["TASK"]))
373
+ capistrano_nomad_execute_nomad_command(
374
+ :alloc,
375
+ :logs,
376
+ options.merge(namespace: namespace, task: task_details[:name]),
377
+ task_details[:alloc_id],
378
+ )
379
+ else
380
+ # If task can't be determined choose a random allocation
381
+ capistrano_nomad_execute_nomad_command(
382
+ :alloc,
383
+ :logs,
384
+ options.merge(namespace: namespace, job: true),
385
+ name,
386
+ )
387
+ end
388
+ end
389
+
390
+ def capistrano_nomad_tail_job_logs(*args, **options)
391
+ capistrano_nomad_display_job_logs(*args, **options.merge(tail: true, n: 50))
344
392
  end
@@ -55,7 +55,7 @@ namespace :nomad do
55
55
  end
56
56
 
57
57
  desc "Create missing and remove unused namespaces"
58
- task :replace_namespaces do
58
+ task :modify_namespaces do
59
59
  output = capistrano_nomad_capture_nomad_command(:namespace, :list, t: "'{{range .}}{{ .Name }}|{{end}}'")
60
60
  current_namespaces = output.split("|").compact.map(&:to_sym)
61
61
  desired_namespaces = fetch(:nomad_jobs).keys
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano-nomad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Hu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-06 00:00:00.000000000 Z
11
+ date: 2023-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport