capistrano-nomad 0.16.2 → 0.16.4

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: b933f1a90e14c606e77ab78668614d0ddaa7dcceefe07ed3257b74f5dfc472f2
4
+ data.tar.gz: 9987bc206fd62a34d16f645ad95b32e6fbb07de96b05bdcb3ae973311b7a0c5a
5
5
  SHA512:
6
- metadata.gz: ae43c669814a9ba3ccd7462d4cd862b97cc79bdeefed86966fa251fbe45fa184cb8ac570b35bfd811907f1e276af4c6c6c6e3d57823c4fdea6922f891822b2c1
7
- data.tar.gz: bb552095171a362745db10f63a45c3ca24ee9f7fb715d52738fa18802472798b695090a55a5d79bb5a7457dbad84d44e1b03b5112a132f7cdda1b0d1c8ca8051
6
+ metadata.gz: 16f9c69beec42b4633a368f2de327ff266dd06514685e2c626323f7cd5c483dc0df4bc57b3effc8e10848e62300da7b00c67f9ed8c2a2e3b52501fe831fb4814
7
+ data.tar.gz: 911b1f701fabfd92e31b23d79b2e15259726dad29e7bc2c9cbd2c1ff7b897add13d57a97dc133fb03ffbb374b809ed290fc86dceb5bd302eef4a28fc85bb9a87
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.16.4]
4
+
5
+ - Request raw JSON when inspecting periodic jobs so the force task reads the deployed periodic configuration
6
+
7
+ ## [0.16.3]
8
+
9
+ - Add a generated `:force` task to launch a periodic child through the configured manager host and Nomad token
10
+ - Reject non-periodic and disabled periodic jobs before submitting a force request
11
+ - Fail the Capistrano command when job inspection or periodic force fails
12
+
3
13
  ## [0.16.2]
4
14
 
5
15
  - 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.4)
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.4"
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.merge(json: true), 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,25 @@ 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
+ case "$*" in
48
+ *-json*) ;;
49
+ *) echo '{"Job":{}}'; exit 0;;
50
+ esac
51
+ printf '%s\n' "$NOMAD_TEST_JOB"
52
+ exit "${NOMAD_TEST_INSPECT_EXIT:-0}"
53
+ fi
54
+ [ "$2 $3" = 'periodic force' ] || exit 99
55
+ if [ "$NOMAD_TEST_EXIT" = 0 ]; then
56
+ echo 'Created periodic child'
57
+ else
58
+ echo 'Periodic force rejected' >&2
59
+ fi
60
+ exit "$NOMAD_TEST_EXIT"
61
+ fi
39
62
  [ "$1" = plan ] || exit 99
40
63
  cat "$2"
41
64
  case "$NOMAD_TEST_EXIT" in
@@ -65,6 +88,52 @@ class UploadPlanTest < Minitest::Test
65
88
  end
66
89
  end
67
90
 
91
+ [0, 1, 2].each do |exit_code|
92
+ File.write("#{root}/commands", "")
93
+ output, status = Open3.capture2e(
94
+ { "NOMAD_TEST_EXIT" => exit_code.to_s, "NOMAD_TEST_JOB" => '{"Periodic":{"Enabled":true}}' },
95
+ "bundle", "exec", "cap", "test", "nomad:spleencheese:myanonamouse-maintenance:force",
96
+ chdir: root,
97
+ )
98
+
99
+ assert_equal("job inspect -namespace=spleencheese -json myanonamouse-maintenance\njob periodic force -namespace=spleencheese myanonamouse-maintenance\n", File.read("#{root}/commands"), output)
100
+ assert_equal(exit_code.zero?, status.success?, output)
101
+ assert_includes(output, exit_code.zero? ? "Created periodic child" : "Periodic force rejected")
102
+ end
103
+
104
+ ['{"Periodic":null}', '{"Periodic":{"Enabled":false}}'].each do |job|
105
+ File.write("#{root}/commands", "")
106
+ output, status = Open3.capture2e(
107
+ { "NOMAD_TEST_JOB" => job },
108
+ "bundle", "exec", "cap", "test", "nomad:example:force",
109
+ chdir: root,
110
+ )
111
+
112
+ refute(status.success?, output)
113
+ assert_includes(output, "Job default/example is not an enabled periodic job")
114
+ assert_equal("job inspect -namespace=default -json example\n", File.read("#{root}/commands"), output)
115
+ end
116
+
117
+ File.write("#{root}/commands", "")
118
+ output, status = Open3.capture2e(
119
+ { "NOMAD_TEST_JOB" => '{"Periodic":{"Enabled":true}}', "NOMAD_TEST_INSPECT_EXIT" => "1" },
120
+ "bundle", "exec", "cap", "test", "nomad:example:force",
121
+ chdir: root,
122
+ )
123
+
124
+ refute(status.success?, output)
125
+ assert_equal("job inspect -namespace=default -json example\n", File.read("#{root}/commands"), output)
126
+
127
+ File.write("#{root}/commands", "")
128
+ output, status = Open3.capture2e(
129
+ { "NOMAD_TEST_JOB" => '{"Periodic":{"Enabled":true}}', "NOMAD_TEST_EXIT" => "0" },
130
+ "bundle", "exec", "cap", "test", "nomad:example:force",
131
+ chdir: root,
132
+ )
133
+
134
+ assert(status.success?, output)
135
+ assert_equal("job inspect -namespace=default -json example\njob periodic force -namespace=default example\n", File.read("#{root}/commands"), output)
136
+
68
137
  if ENV["NOMAD_REAL_BINARY"]
69
138
  FileUtils.cp(ENV.fetch("NOMAD_REAL_BINARY"), "#{root}/bin/nomad")
70
139
  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.4
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