capistrano-nomad 0.11.1 → 0.12.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
  SHA256:
3
- metadata.gz: 3c21ac8313227691d585855b23b06182c72b04000c04f1b74d04825e446febb2
4
- data.tar.gz: 19e9c17ef14d2342bd849f03ef4e3515d04f5c61d02ded52edd656bc08c0acd8
3
+ metadata.gz: d14fcb69aa16bc0815770535ac91e2eef6ef6bbbebc372b2278267912389f68d
4
+ data.tar.gz: 8a031005e8a27ce2ac50a6cd8f026290b4f6bd3e482b3acc6d819adfb668cc3a
5
5
  SHA512:
6
- metadata.gz: ecd8ecae5c1e062469e6e308ca68fa09b913edd635c05bc5b1cb80331a7e5ec550f626168a103df2b48f1f465bd846b9054b01f245029c968fc97aa052b8186a
7
- data.tar.gz: 9a58048d208327c150c81800eb0f200220494ae48a656c8bb293aaf55b1799d90a8668b09b1ab7be69c7e48c899295a0e64873e2928198ac644276f582205c78
6
+ metadata.gz: fc3c5fc5c9a43e97e3277e53120ffcf88f14e9436d34742fad563221d8a003e7d9a2d56c93cbd7dad8676f711f86235b6715cd259a037db320c5876bc8f62bf2
7
+ data.tar.gz: a21adbbb7fc30eac19788733406fbeae0840c8bfab8ece91ee69fc202a35ef200ae8c62000321a6b3562d7d56bb1c4d7408189fcc6276e1d6e7fdbb8407e2494
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capistrano-nomad (0.11.1)
4
+ capistrano-nomad (0.12.0)
5
5
  activesupport (<= 7.0.8)
6
6
  byebug
7
7
  capistrano (~> 3.0)
data/README.md CHANGED
@@ -37,7 +37,7 @@ Within `deploy.rb`
37
37
  set :nomad_jobs_path, "nomad/jobs"
38
38
  set :nomad_var_files_path, "nomad/vars"
39
39
 
40
- # Determines base URL to use when opening job in web UI
40
+ # Determines base URL to use when opening job in web UI
41
41
  set :nomad_ui_url, "http://localhost:4646"
42
42
 
43
43
  # Make variables available to all template .erb files
@@ -162,6 +162,14 @@ cap production nomad:app:stderr
162
162
  cap production nomad:analytics:grafana:follow
163
163
  ```
164
164
 
165
+ Revert jobs
166
+
167
+ ```shell
168
+ cap production nomad:app:revert
169
+ cap production nomad:app:revert VERSION=4
170
+ cap production nomad:app:revert DOCKER_IMAGE=v1.4.4
171
+ ```
172
+
165
173
  Open job in web UI
166
174
 
167
175
  ```shell
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "capistrano-nomad"
5
- spec.version = "0.11.1"
5
+ spec.version = "0.12.0"
6
6
  spec.authors = ["James Hu"]
7
7
 
8
8
  spec.summary = "Capistrano plugin for deploying and managing Nomad jobs"
@@ -44,7 +44,7 @@ def nomad_job(name, attributes = {})
44
44
  attributes[:tags] ||= []
45
45
 
46
46
  # Tags added to namespace should be added to all jobs within
47
- if (nomad_namespace_options = capistrano_nomad_fetch_namespace_options(@nomad_namespace))
47
+ if (nomad_namespace_options = capistrano_nomad_fetch_namespace_options(namespace: @nomad_namespace))
48
48
  attributes[:tags] += nomad_namespace_options[:tags] || []
49
49
  end
50
50
 
@@ -120,6 +120,14 @@ def nomad_job(name, attributes = {})
120
120
  capistrano_nomad_restart_jobs([name], namespace: namespace)
121
121
  end
122
122
 
123
+ desc "Revert #{description_name} job. Specify version with VERSION. Specify targeting tasks with docker image with DOCKER_IMAGE. If none specified, it will revert to previous version"
124
+ task :revert do
125
+ capistrano_nomad_revert_jobs([name], ENV["VERSION"].presence,
126
+ namespace: namespace,
127
+ docker_image: ENV["DOCKER_IMAGE"],
128
+ )
129
+ end
130
+
123
131
  desc "Purge #{description_name} job"
124
132
  task :purge do
125
133
  capistrano_nomad_purge_jobs([name], namespace: namespace, is_detached: false)
@@ -29,7 +29,9 @@ def capistrano_nomad_ensure_absolute_path(path)
29
29
  path[0] == "/" ? path : "/#{path}"
30
30
  end
31
31
 
32
- def capistrano_nomad_build_file_path(parent_path, basename, kind: nil, namespace: :default)
32
+ def capistrano_nomad_build_file_path(parent_path, basename, kind: nil, **options)
33
+ capistrano_nomad_ensure_options!(**options)
34
+ namespace = options[:namespace]
33
35
  segments = [parent_path]
34
36
 
35
37
  unless namespace == :default
@@ -41,7 +43,7 @@ def capistrano_nomad_build_file_path(parent_path, basename, kind: nil, namespace
41
43
 
42
44
  # Otherwise path can be overriden of where files belonging to namespace are stored locally
43
45
  else
44
- namespace_options = capistrano_nomad_fetch_namespace_options(namespace)
46
+ namespace_options = capistrano_nomad_fetch_namespace_options(namespace: namespace)
45
47
 
46
48
  segments << (namespace_options[:path] || namespace)
47
49
  end
@@ -132,14 +134,15 @@ def capistrano_nomad_capture_nomad_command(*args, **options)
132
134
  output
133
135
  end
134
136
 
135
- def capistrano_nomad_find_job_task_details(name, namespace: :default, task: nil)
137
+ def capistrano_nomad_find_job_task_details(name, task: nil, **options)
138
+ capistrano_nomad_ensure_options!(**options)
136
139
  task = task.presence || name
137
140
 
138
141
  # Find alloc id that contains task that is also running
139
142
  allocs_output = capistrano_nomad_capture_nomad_command(
140
143
  :job,
141
144
  :allocs,
142
- { namespace: namespace, t: "'{{range .}}{{ .ID }},{{ .ClientStatus }},{{ .TaskGroup }}|{{end}}'" },
145
+ options.merge(t: "'{{range .}}{{ .ID }},{{ .ClientStatus }},{{ .TaskGroup }}|{{end}}'"),
143
146
  name,
144
147
  )
145
148
  alloc_id = allocs_output
@@ -154,7 +157,7 @@ def capistrano_nomad_find_job_task_details(name, namespace: :default, task: nil)
154
157
  tasks_output = capistrano_nomad_capture_nomad_command(
155
158
  :alloc,
156
159
  :status,
157
- { namespace: namespace, t: "'{{range $key, $value := .TaskStates}}{{ $key }},{{ .State }}|{{end}}'" },
160
+ options.merge(t: "'{{range $key, $value := .TaskStates}}{{ $key }},{{ .State }}|{{end}}'"),
158
161
  alloc_id,
159
162
  )
160
163
  tasks_by_score = tasks_output.split("|").each_with_object({}) do |task_output, hash|
@@ -174,13 +177,15 @@ def capistrano_nomad_find_job_task_details(name, namespace: :default, task: nil)
174
177
  }
175
178
  end
176
179
 
177
- def capistrano_nomad_exec_within_job(name, command, namespace: :default, task: nil)
180
+ def capistrano_nomad_exec_within_job(name, command, task: nil, **options)
181
+ capistrano_nomad_ensure_options!(**options)
182
+
178
183
  capistrano_nomad_run_remotely do
179
- if (task_details = capistrano_nomad_find_job_task_details(name, namespace: namespace, task: task))
184
+ if (task_details = capistrano_nomad_find_job_task_details(name, task: task, **options))
180
185
  capistrano_nomad_execute_nomad_command(
181
186
  :alloc,
182
187
  :exec,
183
- { namespace: namespace, task: task_details[:name] },
188
+ options.merge(task: task_details[:name]),
184
189
  task_details[:alloc_id],
185
190
  command,
186
191
  )
@@ -189,7 +194,7 @@ def capistrano_nomad_exec_within_job(name, command, namespace: :default, task: n
189
194
  capistrano_nomad_execute_nomad_command(
190
195
  :alloc,
191
196
  :exec,
192
- { namespace: namespace, job: true },
197
+ options.merge(job: true),
193
198
  task,
194
199
  command,
195
200
  )
@@ -246,19 +251,30 @@ def capistrano_nomad_upload(local_path:, remote_path:, erb_vars: {})
246
251
  end
247
252
  end
248
253
 
249
- def capistrano_nomad_fetch_namespace_options(namespace)
250
- fetch(:nomad_namespaces)&.dig(namespace)
254
+ def capistrano_nomad_ensure_options!(**options)
255
+ options[:namespace] ||= :default
256
+ end
257
+
258
+ def capistrano_nomad_fetch_namespace_options(**options)
259
+ capistrano_nomad_ensure_options!(**options)
260
+
261
+ fetch(:nomad_namespaces)&.dig(options[:namespace])
251
262
  end
252
263
 
253
- def capistrano_nomad_fetch_job_options(name, *args, namespace: :default)
254
- fetch(:nomad_jobs).dig(namespace, name.to_sym, *args)
264
+ def capistrano_nomad_fetch_job_options(name, *args, **options)
265
+ capistrano_nomad_ensure_options!(**options)
266
+
267
+ fetch(:nomad_jobs).dig(options[:namespace], name.to_sym, *args)
255
268
  end
256
269
 
257
270
  def capistrano_nomad_fetch_job_var_files(name, **options)
258
271
  capistrano_nomad_fetch_job_options(name, :var_files, **options) || []
259
272
  end
260
273
 
261
- def capistrano_nomad_fetch_jobs_names_by_namespace(namespace: :default)
274
+ def capistrano_nomad_fetch_jobs_names_by_namespace(**options)
275
+ capistrano_nomad_ensure_options!(**options)
276
+ namespace = options[:namespace]
277
+
262
278
  # Can pass tags via command line (e.g. TAG=foo or TAGS=foo,bar)
263
279
  tags =
264
280
  [ENV["TAG"], ENV["TAGS"]].map do |tag_args|
@@ -281,8 +297,10 @@ def capistrano_nomad_fetch_jobs_names_by_namespace(namespace: :default)
281
297
  end
282
298
  end
283
299
 
284
- def capistrano_nomad_fetch_jobs_docker_image_types(names, namespace: :default)
285
- names.map { |n| fetch(:nomad_jobs).dig(namespace, n.to_sym, :docker_image_types) }.flatten.compact.uniq
300
+ def capistrano_nomad_fetch_jobs_docker_image_types(names, **options)
301
+ capistrano_nomad_ensure_options!(**options)
302
+
303
+ names.map { |n| fetch(:nomad_jobs).dig(options[:namespace], n.to_sym, :docker_image_types) }.flatten.compact.uniq
286
304
  end
287
305
 
288
306
  def capistrano_nomad_define_group_tasks(namespace:)
@@ -350,6 +368,13 @@ def capistrano_nomad_define_group_tasks(namespace:)
350
368
  end
351
369
  end
352
370
 
371
+ desc "Revert #{nomad_namespace} jobs"
372
+ task :revert do
373
+ capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
374
+ capistrano_nomad_revert_jobs(names, namespace: jobs_namespace)
375
+ end
376
+ end
377
+
353
378
  desc "Stop #{nomad_namespace} jobs"
354
379
  task :stop do
355
380
  capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
@@ -433,24 +458,26 @@ def capistrano_nomad_plan_jobs(names, **options)
433
458
  end
434
459
  end
435
460
 
436
- def capistrano_nomad_run_jobs(names, namespace: :default, is_detached: true)
461
+ def capistrano_nomad_run_jobs(names, is_detached: true, **options)
462
+ capistrano_nomad_ensure_options!(**options)
463
+
437
464
  names.each do |name|
438
465
  run_options = {
439
- namespace: namespace,
466
+ namespace: options[:namespace],
440
467
  detach: is_detached,
441
468
 
442
469
  # Don't reset counts since they may have been scaled
443
470
  preserve_counts: true,
444
471
  }
445
472
 
446
- capistrano_nomad_fetch_job_var_files(name, namespace: namespace).each do |var_file|
447
- run_options[:var_file] = capistrano_nomad_build_release_var_file_path(var_file, namespace: namespace)
473
+ capistrano_nomad_fetch_job_var_files(name, **options).each do |var_file|
474
+ run_options[:var_file] = capistrano_nomad_build_release_var_file_path(var_file, **options)
448
475
  end
449
476
 
450
477
  capistrano_nomad_execute_nomad_command(
451
478
  :run,
452
479
  run_options,
453
- capistrano_nomad_build_release_job_path(name, namespace: namespace),
480
+ capistrano_nomad_build_release_job_path(name, **options),
454
481
  )
455
482
  end
456
483
  end
@@ -494,6 +521,8 @@ def capistrano_nomad_deploy_jobs(names, **options)
494
521
  end
495
522
 
496
523
  def capistrano_nomad_restart_jobs(names, **options)
524
+ capistrano_nomad_ensure_options!(**options)
525
+
497
526
  names.each do |name|
498
527
  # Automatic yes to prompts. If set, the command automatically restarts multi-region jobs only in the region targeted
499
528
  # by the command, ignores batch errors, and automatically proceeds with the remaining batches without waiting
@@ -502,27 +531,75 @@ def capistrano_nomad_restart_jobs(names, **options)
502
531
  end
503
532
 
504
533
  def capistrano_nomad_stop_jobs(names, **options)
534
+ capistrano_nomad_ensure_options!(**options)
535
+
505
536
  names.each do |name|
506
537
  capistrano_nomad_execute_nomad_command(:job, :stop, options, name)
507
538
  end
508
539
  end
509
540
 
510
- def capistrano_nomad_purge_jobs(names, namespace: :default, is_detached: true)
541
+ def capistrano_nomad_purge_jobs(names, is_detached: true, **options)
542
+ capistrano_nomad_ensure_options!(**options)
543
+
544
+ names.each do |name|
545
+ capistrano_nomad_execute_nomad_command(:stop, options.reverse_merge(purge: true, detach: is_detached), name)
546
+ end
547
+ end
548
+
549
+ def capistrano_nomad_revert_jobs(names, version, docker_image: nil, **options)
550
+ capistrano_nomad_ensure_options!(**options)
551
+ versions_by_job_name = {}
552
+
511
553
  names.each do |name|
512
- capistrano_nomad_execute_nomad_command(:stop, { namespace: namespace, purge: true, detach: is_detached }, name)
554
+ history_output_json = capistrano_nomad_display_job_history(name, **options.reverse_merge(json: true))
555
+ history_output = JSON.parse(history_output_json)
556
+
557
+ versions_by_job_name[name] = if docker_image
558
+ # Find job history with matching docker image
559
+ docker_image_job_history = history_output.find do |job_history|
560
+ task_images = job_history.dig("TaskGroups")
561
+ .map { |g| g.dig("Tasks").map { |t| t.dig("Config", "image") } }
562
+ .flatten
563
+ .compact
564
+
565
+ task_images.any? { |image| image.include?(docker_image) }
566
+ end
567
+
568
+ unless docker_image_job_history
569
+ raise ArgumentError, "No job history found for job #{name} with docker image: #{docker_image}"
570
+ end
571
+
572
+ docker_image_job_history.dig("Version")
573
+ else
574
+ # If no version specified then revert to previous version
575
+ history_output[1].dig("Version")
576
+ end
577
+ end
578
+
579
+ versions_by_job_name.each do |name, version|
580
+ capistrano_nomad_execute_nomad_command(:job, :revert, options, name, version)
513
581
  end
514
582
  end
515
583
 
584
+ def capistrano_nomad_display_job_history(name, **options)
585
+ capistrano_nomad_ensure_options!(**options)
586
+
587
+ capistrano_nomad_capture_nomad_command(:job, :history, options, name)
588
+ end
589
+
516
590
  def capistrano_nomad_display_job_status(name, **options)
591
+ capistrano_nomad_ensure_options!(**options)
592
+
517
593
  capistrano_nomad_execute_nomad_command(:status, options, name)
518
594
  end
519
595
 
520
- def capistrano_nomad_display_job_logs(name, namespace: :default, **options)
521
- if (task_details = capistrano_nomad_find_job_task_details(name, namespace: namespace, task: ENV["TASK"]))
596
+ def capistrano_nomad_display_job_logs(name, **options)
597
+ if (task_details = capistrano_nomad_find_job_task_details(name, **options.slice(:namespace).reverse_merge(task: ENV["TASK"])
598
+ ))
522
599
  capistrano_nomad_execute_nomad_command(
523
600
  :alloc,
524
601
  :logs,
525
- options.merge(namespace: namespace, task: task_details[:name]),
602
+ options.reverse_merge(task: task_details[:name]),
526
603
  task_details[:alloc_id],
527
604
  )
528
605
  else
@@ -530,7 +607,7 @@ def capistrano_nomad_display_job_logs(name, namespace: :default, **options)
530
607
  capistrano_nomad_execute_nomad_command(
531
608
  :alloc,
532
609
  :logs,
533
- options.merge(namespace: namespace, job: true),
610
+ options.reverse_merge(job: true),
534
611
  name,
535
612
  )
536
613
  end
@@ -540,7 +617,7 @@ def capistrano_nomad_tail_job_logs(*args, **options)
540
617
  capistrano_nomad_display_job_logs(*args, **options.merge(tail: true, n: 50))
541
618
  end
542
619
 
543
- def capistrano_nomad_open_job_ui(name, namespace: :default)
620
+ def capistrano_nomad_open_job_ui(name, namespace: nil)
544
621
  run_locally do
545
622
  url = "#{fetch(:nomad_ui_url)}/ui/jobs/#{name}"
546
623
  url += "@#{namespace}" if namespace
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.11.1
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Hu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-10 00:00:00.000000000 Z
11
+ date: 2025-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport