capistrano-nomad 0.6.4 → 0.6.5

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