capistrano-nomad 0.16.2 → 0.16.3

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: 408063f04626ace5215b232901a72869fb5aabfe336017ce0e33aa1f1b53b02c
4
- data.tar.gz: 4f3019490d7fa5042f1efe94ba7bedf1725fe3f25d391c19ae87fd45b051d79e
3
+ metadata.gz: 56224de9c341668aba37b58d2b0b404decfce7ee5f488364c325319fea262572
4
+ data.tar.gz: 49275ee4444defa39b4de8e6d193342d63b8a1edd08deb6f4946451c8c4b9cf2
5
5
  SHA512:
6
- metadata.gz: ae43c669814a9ba3ccd7462d4cd862b97cc79bdeefed86966fa251fbe45fa184cb8ac570b35bfd811907f1e276af4c6c6c6e3d57823c4fdea6922f891822b2c1
7
- data.tar.gz: bb552095171a362745db10f63a45c3ca24ee9f7fb715d52738fa18802472798b695090a55a5d79bb5a7457dbad84d44e1b03b5112a132f7cdda1b0d1c8ca8051
6
+ metadata.gz: 10e0218ac88de63102cb0af504e7eec43985535516d6fcd91c5404224003b4a652e500bef48895707f4532ca83ebac128aef143d6b38571e490eca632bac91c2
7
+ data.tar.gz: 7b53eee7196177a352c757527911ccfb601c8075918f49c8a3557cfc61da571fb707341802affb138fbcb903b13c92e09a619431a6f657ee2bc651ddced4df5f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.16.3]
4
+
5
+ - Add a generated `:force` task to launch a periodic child through the configured manager host and Nomad token
6
+ - Reject non-periodic and disabled periodic jobs before submitting a force request
7
+ - Fail the Capistrano command when job inspection or periodic force fails
8
+
3
9
  ## [0.16.2]
4
10
 
5
11
  - Fail `upload_plan` and other plan operations when Nomad rejects a job or cannot determine the plan
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capistrano-nomad (0.16.2)
4
+ capistrano-nomad (0.16.3)
5
5
  activesupport (<= 7.0.8)
6
6
  byebug
7
7
  capistrano (~> 3.0)
data/README.md CHANGED
@@ -145,6 +145,15 @@ cap production nomad:analytics:grafana:restart
145
145
  cap production nomad:postgres:status
146
146
  ```
147
147
 
148
+ Force a periodic job without uploading, deploying, or restarting its parent:
149
+
150
+ ```shell
151
+ cap production nomad:maintenance:force
152
+ cap production nomad:analytics:archive:force
153
+ ```
154
+
155
+ The per-job `:force` task checks the deployed job before running `nomad job periodic force`. Non-periodic and disabled periodic jobs fail with a clear error. Inspection and force failures produce a nonzero Capistrano exit. The task uses the configured manager host and `:nomad_token`, like other job actions.
156
+
148
157
  Tasks can apply across all namespaces or be filtered by namespaces or tags
149
158
 
150
159
  ```shell
@@ -213,7 +222,7 @@ cap production nomad:version
213
222
 
214
223
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
215
224
 
216
- Run `bundle exec rake test` to test the generated Capistrano `upload_plan` task through the CLI. The tests use local transport and controlled Nomad exit codes. To also check rejection of invalid HCL with a real Nomad binary, run `NOMAD_REAL_BINARY=/path/to/nomad bundle exec rake test`.
225
+ Run `bundle exec rake test` to test the generated Capistrano `upload_plan` and `force` tasks through the CLI. The tests use local transport and controlled Nomad exit codes. Force coverage checks the job, namespace, token, rejected requests, and non-periodic jobs. To also check rejection of invalid HCL with a real Nomad binary, run `NOMAD_REAL_BINARY=/path/to/nomad bundle exec rake test`.
217
226
 
218
227
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version in `capistrano-nomad.gemspec`, run `bundle install`, and update `CHANGELOG.md`. Run `bundle exec rake test` and commit the release changes. Then run `bundle exec rake release`, which creates the version tag, pushes the release commit and tag, and publishes the gem to [RubyGems](https://rubygems.org).
219
228
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "capistrano-nomad"
5
- spec.version = "0.16.2"
5
+ spec.version = "0.16.3"
6
6
  spec.authors = ["James Hu"]
7
7
 
8
8
  spec.summary = "Capistrano plugin for deploying and managing Nomad jobs"
@@ -91,6 +91,11 @@ def nomad_job(name, attributes = {})
91
91
  capistrano_nomad_run_jobs([name], namespace: namespace, is_detached: capistrano_nomad_job_detached_overridden?)
92
92
  end
93
93
 
94
+ desc("Force a new periodic child of #{description_name} job")
95
+ task(:force) do
96
+ capistrano_nomad_force_job(name, namespace: namespace)
97
+ end
98
+
94
99
  desc("Purge and run #{description_name} job again")
95
100
  task(:rerun) do
96
101
  capistrano_nomad_rerun_jobs([name], namespace: namespace, is_detached: capistrano_nomad_job_detached_overridden?)
@@ -142,7 +142,7 @@ def capistrano_nomad_plan_command?(args)
142
142
  args.first.to_s == "plan" || args.take(2).map(&:to_s) == ["job", "plan"]
143
143
  end
144
144
 
145
- def capistrano_nomad_run_nomad_command(kind, *args)
145
+ def capistrano_nomad_run_nomad_command(kind, *args, raise_on_non_zero_exit: false)
146
146
  converted_args = args.each_with_object([]) do |arg, collection|
147
147
  # If hash then convert it as options
148
148
  if arg.is_a?(Hash)
@@ -173,30 +173,30 @@ def capistrano_nomad_run_nomad_command(kind, *args)
173
173
  # Nomad returns 1 for a successful plan with changes; all other nonzero exits are errors
174
174
  public_send(kind, :nomad, *converted_args, '; status=$?; if [ "$status" -eq 1 ]; then exit 0; else exit "$status"; fi', raise_on_non_zero_exit: true)
175
175
  else
176
- # Ignore errors for commands other than plans
177
- public_send(kind, :nomad, *converted_args, raise_on_non_zero_exit: false)
176
+ # Preserve unchecked execution unless the caller requires failures to propagate
177
+ public_send(kind, :nomad, *converted_args, raise_on_non_zero_exit: raise_on_non_zero_exit)
178
178
  end
179
179
  end
180
180
  end
181
181
 
182
- def capistrano_nomad_execute_nomad_command(*args, **options)
182
+ def capistrano_nomad_execute_nomad_command(*args, raise_on_non_zero_exit: false, **options)
183
183
  capistrano_nomad_run_remotely do |host|
184
- if capistrano_nomad_plan_command?(args)
185
- # Use SSHKit's checked execution because the interactive backend does not raise on failure
186
- capistrano_nomad_run_nomad_command(:execute, *args, **options)
184
+ if capistrano_nomad_plan_command?(args) || raise_on_non_zero_exit
185
+ # Use checked execution when failures must propagate because the interactive backend does not raise
186
+ capistrano_nomad_run_nomad_command(:execute, *args, options, raise_on_non_zero_exit: raise_on_non_zero_exit)
187
187
  else
188
188
  run_interactively(host) do
189
- capistrano_nomad_run_nomad_command(:execute, *args, **options)
189
+ capistrano_nomad_run_nomad_command(:execute, *args, options)
190
190
  end
191
191
  end
192
192
  end
193
193
  end
194
194
 
195
- def capistrano_nomad_capture_nomad_command(*args, **options)
195
+ def capistrano_nomad_capture_nomad_command(*args, raise_on_non_zero_exit: false, **options)
196
196
  output = nil
197
197
 
198
198
  capistrano_nomad_run_remotely do
199
- output = capistrano_nomad_run_nomad_command(:capture, *args, **options)
199
+ output = capistrano_nomad_run_nomad_command(:capture, *args, options, raise_on_non_zero_exit: raise_on_non_zero_exit)
200
200
  end
201
201
 
202
202
  output
@@ -634,6 +634,17 @@ def capistrano_nomad_restart_jobs(names, **options)
634
634
  end
635
635
  end
636
636
 
637
+ def capistrano_nomad_force_job(name, **options)
638
+ capistrano_nomad_ensure_options!(options)
639
+ job = JSON.parse(capistrano_nomad_capture_nomad_command(:job, :inspect, options, name, raise_on_non_zero_exit: true))
640
+
641
+ unless job.dig("Periodic", "Enabled")
642
+ raise ArgumentError, "Job #{options[:namespace]}/#{name} is not an enabled periodic job"
643
+ end
644
+
645
+ capistrano_nomad_execute_nomad_command(:job, :periodic, :force, options, name, raise_on_non_zero_exit: true)
646
+ end
647
+
637
648
  def capistrano_nomad_start_jobs(names, **options)
638
649
  capistrano_nomad_ensure_options!(options)
639
650
 
@@ -23,6 +23,10 @@ class UploadPlanTest < Minitest::Test
23
23
  set :nomad_docker_image_types, {}
24
24
  SSHKit.config.command_map[:nomad] = #{"#{root}/bin/nomad".inspect}
25
25
  nomad_job :example
26
+ set :nomad_token, "force-test-token"
27
+ nomad_namespace :spleencheese do
28
+ nomad_job :"myanonamouse-maintenance"
29
+ end
26
30
  RUBY
27
31
  File.write("#{root}/config/deploy/test.rb", <<~RUBY)
28
32
  server "localhost", roles: [:manager]
@@ -36,6 +40,21 @@ class UploadPlanTest < Minitest::Test
36
40
  FileUtils.chmod(0755, "#{root}/bin/ssh")
37
41
  File.write("#{root}/bin/nomad", <<~SH)
38
42
  #!/bin/sh
43
+ if [ "$1" = job ]; then
44
+ [ "$NOMAD_TOKEN" = force-test-token ] || exit 98
45
+ printf '%s\n' "$*" >> #{root}/commands
46
+ if [ "$2" = inspect ]; then
47
+ printf '%s\n' "$NOMAD_TEST_JOB"
48
+ exit "${NOMAD_TEST_INSPECT_EXIT:-0}"
49
+ fi
50
+ [ "$2 $3" = 'periodic force' ] || exit 99
51
+ if [ "$NOMAD_TEST_EXIT" = 0 ]; then
52
+ echo 'Created periodic child'
53
+ else
54
+ echo 'Periodic force rejected' >&2
55
+ fi
56
+ exit "$NOMAD_TEST_EXIT"
57
+ fi
39
58
  [ "$1" = plan ] || exit 99
40
59
  cat "$2"
41
60
  case "$NOMAD_TEST_EXIT" in
@@ -65,6 +84,52 @@ class UploadPlanTest < Minitest::Test
65
84
  end
66
85
  end
67
86
 
87
+ [0, 1, 2].each do |exit_code|
88
+ File.write("#{root}/commands", "")
89
+ output, status = Open3.capture2e(
90
+ { "NOMAD_TEST_EXIT" => exit_code.to_s, "NOMAD_TEST_JOB" => '{"Periodic":{"Enabled":true}}' },
91
+ "bundle", "exec", "cap", "test", "nomad:spleencheese:myanonamouse-maintenance:force",
92
+ chdir: root,
93
+ )
94
+
95
+ assert_equal("job inspect -namespace=spleencheese myanonamouse-maintenance\njob periodic force -namespace=spleencheese myanonamouse-maintenance\n", File.read("#{root}/commands"), output)
96
+ assert_equal(exit_code.zero?, status.success?, output)
97
+ assert_includes(output, exit_code.zero? ? "Created periodic child" : "Periodic force rejected")
98
+ end
99
+
100
+ ['{"Periodic":null}', '{"Periodic":{"Enabled":false}}'].each do |job|
101
+ File.write("#{root}/commands", "")
102
+ output, status = Open3.capture2e(
103
+ { "NOMAD_TEST_JOB" => job },
104
+ "bundle", "exec", "cap", "test", "nomad:example:force",
105
+ chdir: root,
106
+ )
107
+
108
+ refute(status.success?, output)
109
+ assert_includes(output, "Job default/example is not an enabled periodic job")
110
+ assert_equal("job inspect -namespace=default example\n", File.read("#{root}/commands"), output)
111
+ end
112
+
113
+ File.write("#{root}/commands", "")
114
+ output, status = Open3.capture2e(
115
+ { "NOMAD_TEST_JOB" => '{"Periodic":{"Enabled":true}}', "NOMAD_TEST_INSPECT_EXIT" => "1" },
116
+ "bundle", "exec", "cap", "test", "nomad:example:force",
117
+ chdir: root,
118
+ )
119
+
120
+ refute(status.success?, output)
121
+ assert_equal("job inspect -namespace=default example\n", File.read("#{root}/commands"), output)
122
+
123
+ File.write("#{root}/commands", "")
124
+ output, status = Open3.capture2e(
125
+ { "NOMAD_TEST_JOB" => '{"Periodic":{"Enabled":true}}', "NOMAD_TEST_EXIT" => "0" },
126
+ "bundle", "exec", "cap", "test", "nomad:example:force",
127
+ chdir: root,
128
+ )
129
+
130
+ assert(status.success?, output)
131
+ assert_equal("job inspect -namespace=default example\njob periodic force -namespace=default example\n", File.read("#{root}/commands"), output)
132
+
68
133
  if ENV["NOMAD_REAL_BINARY"]
69
134
  FileUtils.cp(ENV.fetch("NOMAD_REAL_BINARY"), "#{root}/bin/nomad")
70
135
  output, status = Open3.capture2e("bundle", "exec", "cap", "test", "nomad:example:upload_plan", chdir: root)
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.16.2
4
+ version: 0.16.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Hu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-06 00:00:00.000000000 Z
11
+ date: 2026-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport