capistrano-nomad 0.6.4 → 0.7.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: 707ded6b01d5903708a362ef6e98f3c56d5e52fd1557bfad47f1844cb7d6c96e
4
- data.tar.gz: 68ea61e7025400e99c97683b8f3390357a0f04cf5b173846bba378d8bdd5fa3d
3
+ metadata.gz: d227d6f15f433cd6410884afc5572cf9c7e115a7365e00e244d3b95ca320f148
4
+ data.tar.gz: a0cdd0e131319cd0f1a17a926afe70619d24550018de9731c96a610b075300c2
5
5
  SHA512:
6
- metadata.gz: 19a5ee3bcddf663ed3820245f10d0c35dee3cf2d1659b44f61947696c7ea24c263696c3c25d6dba032d14293706f0b9b68ee3e9cdbd41b66309e43eb92a0e297
7
- data.tar.gz: b20019a85564cc4eee312bfaa17934becab4b73c2505c7a0b9511b1759b320c56fda3656b7f2b80b3cd090e347f05f5c0a2ef3e283a078850636c37ea58f20fb
6
+ metadata.gz: fd4aec6aecd6698d4ed23d4e1a2c3d3e8ded18467c1a4d8daee191e29304e2f91f85ca7d604681dba4ec6cb885308f0fb6985fb4e9d952e459fc7859a3b3917a
7
+ data.tar.gz: 6009d60f5bd99e35fdb2961f6b9d860ea9db40ac844cebacf87b21ae58381eeefd35022fb58c7a6b88fa74da7e3acff3ee26a0f9a71a92e1b61495102bf53a21
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.7.0)
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,19 +46,45 @@ set :nomad_erb_vars, (lambda do
46
46
  }
47
47
  end)
48
48
 
49
- # Docker image types
49
+ # Make helpers available to all template .erb files
50
+ nomad_template_helpers do
51
+ def restart_stanza(interval = "1m")
52
+ <<-EOF
53
+ restart {
54
+ interval = "#{interval}"
55
+ attempts = 3
56
+ mode = "delay"
57
+ }
58
+ EOF
59
+ end
60
+ end
61
+
62
+ # Use hosted Docker image
63
+ nomad_docker_image_type :postgres,
64
+ alias_digest: "postgres:5.0.0"
65
+
66
+ # Use Docker image that will be built locally relative to project and push
50
67
  nomad_docker_image_type :backend,
51
68
  path: "local/path/backend",
52
69
  alias: ->(image_type:) { "gcr.io/axsuul/#{image_type}" },
53
70
  target: "release",
54
71
  build_args: { foo: "bar" }
72
+
73
+ # Use Docker image that will be built locally from an absolute path and push
55
74
  nomad_docker_image_type :redis,
56
75
  path: "/absolute/path/redis",
57
76
  alias: "gcr.io/axsuul/redis"
58
77
 
78
+ # Use Docker image that will be built remotely on server
79
+ nomad_docker_image_type :restic,
80
+ path: "containers/restic",
81
+ alias: "my-project/restic:local",
82
+ strategy: :remote_build
83
+
59
84
  # Jobs
60
- nomad_job :frontend
61
85
  nomad_job :backend, docker_image_types: [:backend], var_files: [:rails]
86
+ nomad_job :frontend
87
+ nomad_job :postgres, docker_image_types: [:postgres]
62
88
  nomad_job :redis, docker_image_types: [:redis]
63
89
  nomad_job :"traefik-default", template: :traefik, erb_vars: { role: :default }
64
90
  nomad_job :"traefik-secondary", template: :traefik, erb_vars: { role: :secondary }
@@ -68,21 +94,29 @@ nomad_namespace :analytics do
68
94
  end
69
95
  ```
70
96
 
71
- Deploy all with
97
+ Deploy all jobs
72
98
 
73
99
  ```shell
74
100
  cap production nomad:all:deploy
75
101
  ```
76
102
 
77
- Deploy individually with
103
+ Deploy individual jobs
78
104
 
79
105
  ```shell
80
106
  cap production nomad:app:deploy
81
- cap production nomad:redis:purge
82
107
  cap production nomad:analytics:grafana:deploy
83
108
  ```
84
109
 
85
- Open console with
110
+ Manage jobs
111
+
112
+ ```shell
113
+ cap production nomad:app:stop
114
+ cap production nomad:redis:purge
115
+ cap production nomad:analytics:grafana:restart
116
+ cap production nomad:postgres:status
117
+ ```
118
+
119
+ Open console
86
120
 
87
121
  ```shell
88
122
  cap production nomad:app:console
@@ -90,10 +124,19 @@ cap production nomad:app:console TASK=custom-task-name
90
124
  cap production nomad:analytics:grafana:console
91
125
  ```
92
126
 
127
+ Display logs
128
+
129
+ ```shell
130
+ cap production nomad:app:logs
131
+ cap production nomad:app:stdout
132
+ cap production nomad:app:stderr
133
+ cap production nomad:analytics:grafana:follow
134
+ ```
135
+
93
136
  Create missing and delete unused namespaces
94
137
 
95
138
  ```shell
96
- cap production nomad:all:replace_namespaces
139
+ cap production nomad:all:modify_namespaces
97
140
  ```
98
141
 
99
142
  ## 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.7.0"
6
6
  spec.authors = ["James Hu"]
7
7
 
8
8
  spec.summary = "Capistrano plugin for deploying and managing Nomad jobs"
@@ -5,3 +5,7 @@ end
5
5
  def capistrano_nomad_deep_symbolize_hash_keys(hash)
6
6
  JSON.parse(JSON[hash], symbolize_names: true)
7
7
  end
8
+
9
+ def capistrano_nomad_run_remotely(&block)
10
+ on(roles(:manager), &block)
11
+ end
@@ -21,7 +21,7 @@ end
21
21
  def capistrano_nomad_read_docker_image_types_manifest
22
22
  manifest = {}
23
23
 
24
- on(roles(:manager)) do |_host|
24
+ capistrano_nomad_run_remotely do
25
25
  # Ensure file exists
26
26
  execute("mkdir -p #{shared_path}")
27
27
  execute("touch #{capistrano_nomad_docker_image_types_manifest_path}")
@@ -37,7 +37,7 @@ def capistrano_nomad_read_docker_image_types_manifest
37
37
  end
38
38
 
39
39
  def capistrano_nomad_update_docker_image_types_manifest(image_type, properties = {})
40
- on(roles(:manager)) do |_host|
40
+ capistrano_nomad_run_remotely do
41
41
  # Read and update manifest
42
42
  manifest = capistrano_nomad_read_docker_image_types_manifest
43
43
  manifest[image_type] = (manifest[image_type] || {}).merge(properties.stringify_keys)
@@ -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)
@@ -87,12 +90,7 @@ def capistrano_nomad_build_docker_image_for_type(image_type)
87
90
  args << "--build-arg #{key}=#{value}"
88
91
  end
89
92
 
90
- run_locally do
91
- # If any of these files exist then we're in the middle of rebase so we should interrupt
92
- if ["rebase-merge", "rebase-apply"].any? { |f| File.exist?("#{capistrano_nomad_git.dir.path}/.git/#{f}") }
93
- raise StandardError, "Still in the middle of git rebase, interrupting docker image build"
94
- end
95
-
93
+ docker_build_command = lambda do |path|
96
94
  image_alias_args = args.dup
97
95
 
98
96
  [capistrano_nomad_build_docker_image_alias(image_type)]
@@ -101,31 +99,60 @@ def capistrano_nomad_build_docker_image_for_type(image_type)
101
99
  image_alias_args << "--tag #{tag}"
102
100
  end
103
101
 
104
- execute("docker build #{image_alias_args.join(' ')} #{capistrano_nomad_root.join(attributes[:path])}")
102
+ "docker build #{image_alias_args.join(' ')} #{path}"
103
+ end
104
+
105
+ case attributes[:strategy]
106
+
107
+ # We need to build Docker container locally
108
+ when :local_build, :local_push
109
+ run_locally do
110
+ # If any of these files exist then we're in the middle of rebase so we should interrupt
111
+ if ["rebase-merge", "rebase-apply"].any? { |f| File.exist?("#{capistrano_nomad_git.dir.path}/.git/#{f}") }
112
+ raise StandardError, "Still in the middle of git rebase, interrupting docker image build"
113
+ end
114
+
115
+ execute(docker_build_command.call(capistrano_nomad_root.join(attributes[:path])))
116
+ end
117
+
118
+ # We need to build Docker container remotely
119
+ when :remote_build, :remote_push
120
+ remote_path = Pathname.new(release_path).join(attributes[:path])
121
+ capistrano_nomad_upload(local_path: attributes[:path], remote_path: remote_path)
122
+
123
+ capistrano_nomad_run_remotely do
124
+ execute(docker_build_command.call(remote_path))
125
+ end
105
126
  end
106
127
  end
107
128
 
108
129
  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")
130
+ attributes = fetch(:nomad_docker_image_types)[image_type]
131
+ alias_digest = attributes&.dig(:alias_digest)
132
+
133
+ return false unless [:local_push, :remote_push].include?(attributes[:strategy])
111
134
 
112
135
  run_locally do
113
- interaction_handler = CapistranoNomadDockerPushImageInteractionHandler.new
114
- image_alias = capistrano_nomad_build_docker_image_alias(image_type)
136
+ # Don't push Docker image if alias digest is already passed in
137
+ unless alias_digest
138
+ interaction_handler = CapistranoNomadDockerPushImageInteractionHandler.new
139
+ image_alias = capistrano_nomad_build_docker_image_alias(image_type)
140
+
141
+ # We should not proceed if image cannot be pushed
142
+ unless execute("docker push #{image_alias}", interaction_handler: interaction_handler)
143
+ raise StandardError, "Docker image push unsuccessful!"
144
+ end
115
145
 
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
146
+ return unless is_manifest_updated
120
147
 
121
- return unless is_manifest_updated
148
+ # Has the @sha256:xxxx appended so we have the ability to also target by digest
149
+ alias_digest = "#{image_alias}@#{interaction_handler.last_digest}"
150
+ end
122
151
 
123
152
  # Update image type manifest
124
153
  capistrano_nomad_update_docker_image_types_manifest(image_type,
125
154
  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}",
155
+ alias_digest: alias_digest,
129
156
  )
130
157
  end
131
158
  end
@@ -1,6 +1,14 @@
1
+ require "active_support/core_ext/hash"
2
+
1
3
  def nomad_docker_image_type(image_type, attributes = {})
2
4
  docker_image_types = fetch(:nomad_docker_image_types) || {}
3
- docker_image_types[image_type] = attributes
5
+ docker_image_types[image_type] = attributes.reverse_merge(
6
+ # In case image doesn't get pushed, this will still be populated
7
+ alias_digest: attributes[:alias],
8
+
9
+ # By default build and push Docker image locally
10
+ strategy: :local_push,
11
+ )
4
12
 
5
13
  set(:nomad_docker_image_types, docker_image_types)
6
14
  end
@@ -50,12 +58,12 @@ def nomad_job(name, attributes = {})
50
58
 
51
59
  desc "Run #{description_name} job"
52
60
  task :run do
53
- capistrano_nomad_run_jobs([name], namespace: namespace)
61
+ capistrano_nomad_run_jobs([name], namespace: namespace, is_detached: false)
54
62
  end
55
63
 
56
64
  desc "Purge and run #{description_name} job again"
57
65
  task :rerun do
58
- capistrano_nomad_rerun_jobs([name], namespace: namespace)
66
+ capistrano_nomad_rerun_jobs([name], namespace: namespace, is_detached: false)
59
67
  end
60
68
 
61
69
  desc "Upload and plan #{description_name} job"
@@ -65,22 +73,17 @@ def nomad_job(name, attributes = {})
65
73
 
66
74
  desc "Upload and run #{description_name} job"
67
75
  task :upload_run do
68
- capistrano_nomad_upload_run_jobs([name], namespace: namespace)
76
+ capistrano_nomad_upload_run_jobs([name], namespace: namespace, is_detached: false)
69
77
  end
70
78
 
71
79
  desc "Upload and re-run #{description_name} job"
72
80
  task :upload_rerun do
73
- capistrano_nomad_upload_rerun_jobs([name], namespace: namespace)
81
+ capistrano_nomad_upload_rerun_jobs([name], namespace: namespace, is_detached: false)
74
82
  end
75
83
 
76
84
  desc "Deploy #{description_name} job"
77
85
  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)
86
+ capistrano_nomad_deploy_jobs([name], namespace: namespace, is_detached: false)
84
87
  end
85
88
 
86
89
  desc "Stop #{description_name} job"
@@ -88,6 +91,11 @@ def nomad_job(name, attributes = {})
88
91
  capistrano_nomad_stop_jobs([name], namespace: namespace)
89
92
  end
90
93
 
94
+ desc "Restart #{description_name} job"
95
+ task :restart do
96
+ capistrano_nomad_restart_jobs([name], namespace: namespace)
97
+ end
98
+
91
99
  desc "Purge #{description_name} job"
92
100
  task :purge do
93
101
  capistrano_nomad_purge_jobs([name], namespace: namespace, is_detached: false)
@@ -98,9 +106,32 @@ def nomad_job(name, attributes = {})
98
106
  capistrano_nomad_display_job_status(name, namespace: namespace)
99
107
  end
100
108
 
101
- desc "Open console to #{description_name} job. Specify task by passing TASK environment variable"
109
+ desc "Open console to #{description_name} job. Specify task with TASK, command with CMD"
102
110
  task :console do
103
- capistrano_nomad_exec_within_job(name, "/bin/bash", namespace: namespace, task: ENV["TASK"].presence)
111
+ command = ENV["CMD"].presence || "/bin/bash"
112
+
113
+ capistrano_nomad_exec_within_job(name, command, namespace: namespace, task: ENV["TASK"])
114
+ end
115
+
116
+ desc "Display stdout and stderr of #{description_name} job. Specify task with TASK"
117
+ task :logs do
118
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stdout: true)
119
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stderr: true)
120
+ end
121
+
122
+ desc "Display stdout of #{description_name} job. Specify task with TASK"
123
+ task :stdout do
124
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stdout: true)
125
+ end
126
+
127
+ desc "Display stderr of #{description_name} job. Specify task with TASK"
128
+ task :stderr do
129
+ capistrano_nomad_tail_job_logs(name, namespace: namespace, stderr: true)
130
+ end
131
+
132
+ desc "Follow logs of #{description_name} job. Specify task with TASK"
133
+ task :follow do
134
+ capistrano_nomad_display_job_logs(name, namespace: namespace, f: true)
104
135
  end
105
136
  end
106
137
  end
@@ -116,3 +147,7 @@ def nomad_job(name, attributes = {})
116
147
  end
117
148
  end
118
149
  end
150
+
151
+ def nomad_template_helpers(&block)
152
+ CapistranoNomadErbNamespace.class_eval(&block)
153
+ 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}"
@@ -106,7 +96,7 @@ def capistrano_nomad_run_nomad_command(kind, *args)
106
96
  end
107
97
 
108
98
  def capistrano_nomad_execute_nomad_command(*args)
109
- on(roles(:manager)) do |host|
99
+ capistrano_nomad_run_remotely do |host|
110
100
  run_interactively(host) do
111
101
  capistrano_nomad_run_nomad_command(:execute, *args)
112
102
  end
@@ -116,32 +106,59 @@ end
116
106
  def capistrano_nomad_capture_nomad_command(*args)
117
107
  output = nil
118
108
 
119
- on(roles(:manager)) do |_host|
109
+ capistrano_nomad_run_remotely do
120
110
  output = capistrano_nomad_run_nomad_command(:capture, *args)
121
111
  end
122
112
 
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
129
-
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
116
+ def capistrano_nomad_find_job_task_details(name, namespace: nil, task: nil)
117
+ task = task.presence || name
118
+
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
138
147
 
139
- if alloc_id
148
+ {
149
+ alloc_id: alloc_id,
150
+ name: task,
151
+ }
152
+ end
153
+
154
+ def capistrano_nomad_exec_within_job(name, command, namespace: nil, task: nil)
155
+ capistrano_nomad_run_remotely 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
@@ -157,40 +174,55 @@ def capistrano_nomad_exec_within_job(name, command, namespace: nil, task: nil)
157
174
  end
158
175
  end
159
176
 
160
- def capistrano_nomad_upload_file(local_path:, remote_path:, erb_vars: {})
161
- docker_image_types = fetch(:nomad_docker_image_types)
162
- docker_image_types_manifest = capistrano_nomad_read_docker_image_types_manifest
163
-
164
- # Merge manifest into image types
165
- docker_image_types_manifest.each do |manifest_image_type, manifest_attributes|
166
- docker_image_types[manifest_image_type]&.merge!(manifest_attributes) || {}
167
- end
168
-
169
- # Parse manifest files using ERB
170
- erb = ERB.new(File.open(local_path).read, trim_mode: "-")
171
-
172
- final_erb_vars = {
173
- git_commit_id: fetch(:current_revision) || capistrano_nomad_git_commit_id,
174
- docker_image_types: docker_image_types,
175
- }
176
-
177
- # Add global ERB vars
178
- final_erb_vars.merge!(fetch(:nomad_erb_vars) || {})
177
+ def capistrano_nomad_upload(local_path:, remote_path:, erb_vars: {})
178
+ # If directory upload everything within the directory
179
+ if File.directory?(local_path)
180
+ Dir.glob("#{local_path}/*").each do |path|
181
+ capistrano_nomad_upload(local_path: path, remote_path: "#{remote_path}/#{File.basename(path)}")
182
+ end
183
+ else
184
+ io =
185
+ if File.extname(local_path) == ".erb"
186
+ docker_image_types = fetch(:nomad_docker_image_types)
187
+ docker_image_types_manifest = capistrano_nomad_read_docker_image_types_manifest
188
+
189
+ # Merge manifest into image types
190
+ docker_image_types_manifest.each do |manifest_image_type, manifest_attributes|
191
+ docker_image_types[manifest_image_type]&.merge!(manifest_attributes) || {}
192
+ end
193
+
194
+ # Parse manifest files using ERB
195
+ erb = ERB.new(File.open(local_path).read, trim_mode: "-")
196
+
197
+ final_erb_vars = {
198
+ git_commit_id: fetch(:current_revision) || capistrano_nomad_git_commit_id,
199
+ docker_image_types: docker_image_types,
200
+ }
201
+
202
+ # Add global ERB vars
203
+ final_erb_vars.merge!(fetch(:nomad_template_vars) || {})
204
+
205
+ # Add job-specific ERB vars
206
+ final_erb_vars.merge!(erb_vars)
207
+
208
+ # We use a custom namespace class so that we can include helper methods into the namespace to make them available
209
+ # for template to access
210
+ namespace = CapistranoNomadErbNamespace.new(
211
+ context: self,
212
+ vars: final_erb_vars,
213
+ )
214
+
215
+ StringIO.new(erb.result(namespace.instance_eval { binding }))
216
+ else
217
+ File.open(local_path)
218
+ end
179
219
 
180
- # Add job-specific ERB vars
181
- final_erb_vars.merge!(erb_vars)
220
+ capistrano_nomad_run_remotely do
221
+ # Ensure parent directory exists
222
+ execute(:mkdir, "-p", File.dirname(remote_path))
182
223
 
183
- # We use a custom namespace class so that we can include helper methods into the namespace to make them available for
184
- # template to access
185
- namespace = CapistranoNomadErbNamespace.new(
186
- context: self,
187
- vars: final_erb_vars,
188
- )
189
- erb_io = StringIO.new(erb.result(namespace.instance_eval { binding }))
190
-
191
- on(roles(:manager)) do
192
- execute(:mkdir, "-p", File.dirname(remote_path))
193
- upload!(erb_io, remote_path)
224
+ upload!(io, remote_path)
225
+ end
194
226
  end
195
227
  end
196
228
 
@@ -236,7 +268,7 @@ def capistrano_nomad_upload_jobs(names, *args)
236
268
  uniq_var_files = names.map { |n| capistrano_nomad_fetch_job_var_files(n, *args) }.flatten.uniq
237
269
 
238
270
  uniq_var_files.each do |var_file|
239
- capistrano_nomad_upload_file(
271
+ capistrano_nomad_upload(
240
272
  local_path: capistrano_nomad_build_local_var_file_path(var_file, *args),
241
273
  remote_path: capistrano_nomad_build_release_var_file_path(var_file, *args),
242
274
  )
@@ -252,7 +284,7 @@ def capistrano_nomad_upload_jobs(names, *args)
252
284
  # Can set a custom template instead
253
285
  file_basename = nomad_job_options[:template] || name
254
286
 
255
- capistrano_nomad_upload_file(
287
+ capistrano_nomad_upload(
256
288
  local_path: capistrano_nomad_build_local_job_path(file_basename, *args),
257
289
  remote_path: capistrano_nomad_build_release_job_path(name, *args),
258
290
  erb_vars: erb_vars,
@@ -269,11 +301,11 @@ def capistrano_nomad_plan_jobs(names, *args)
269
301
  end
270
302
  end
271
303
 
272
- def capistrano_nomad_run_jobs(names, namespace: nil)
304
+ def capistrano_nomad_run_jobs(names, namespace: nil, is_detached: true)
273
305
  names.each do |name|
274
306
  run_options = {
275
307
  namespace: namespace,
276
- detach: true,
308
+ detach: is_detached,
277
309
 
278
310
  # Don't reset counts since they may have been scaled
279
311
  preserve_counts: true,
@@ -292,44 +324,52 @@ def capistrano_nomad_run_jobs(names, namespace: nil)
292
324
  end
293
325
 
294
326
  # Remove job and run again
295
- def capistrano_nomad_rerun_jobs(names)
327
+ def capistrano_nomad_rerun_jobs(names, **options)
328
+ general_options = options.slice!(:is_detached)
329
+
296
330
  names.each do |name|
297
331
  # Wait for jobs to be purged before running again
298
- capistrano_nomad_purge_jobs([name], is_detached: false)
332
+ capistrano_nomad_purge_jobs([name], **general_options.merge(is_detached: false))
299
333
 
300
- capistrano_nomad_run_jobs([name])
334
+ capistrano_nomad_run_jobs([name], **general_options.merge(options))
301
335
  end
302
336
  end
303
337
 
304
- def capistrano_nomad_upload_plan_jobs(names, *args)
305
- capistrano_nomad_upload_jobs(names, *args)
306
- capistrano_nomad_plan_jobs(names, *args)
338
+ def capistrano_nomad_upload_plan_jobs(names, **options)
339
+ capistrano_nomad_upload_jobs(names, **options)
340
+ capistrano_nomad_plan_jobs(names, **options)
307
341
  end
308
342
 
309
- def capistrano_nomad_upload_run_jobs(names, *args)
310
- capistrano_nomad_upload_jobs(names, *args)
311
- capistrano_nomad_run_jobs(names, *args)
343
+ def capistrano_nomad_upload_run_jobs(names, **options)
344
+ general_options = options.slice!(:is_detached)
345
+
346
+ capistrano_nomad_upload_jobs(names, **general_options)
347
+ capistrano_nomad_run_jobs(names, **general_options.merge(options))
312
348
  end
313
349
 
314
- def capistrano_nomad_upload_rerun_jobs(names, *args)
315
- capistrano_nomad_upload_jobs(names, *args)
316
- capistrano_nomad_rerun_jobs(names, *args)
350
+ def capistrano_nomad_upload_rerun_jobs(names, **options)
351
+ general_options = options.slice!(:is_detached)
352
+
353
+ capistrano_nomad_upload_jobs(names, **general_options)
354
+ capistrano_nomad_rerun_jobs(names, **general_options.merge(options))
317
355
  end
318
356
 
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)
357
+ def capistrano_nomad_deploy_jobs(names, **options)
358
+ general_options = options.slice!(:is_detached)
359
+
360
+ capistrano_nomad_assemble_jobs_docker_images(names, **general_options)
361
+ capistrano_nomad_upload_run_jobs(names, **general_options.merge(options))
322
362
  end
323
363
 
324
- def capistrano_nomad_stop_jobs(names, namespace: nil)
364
+ def capistrano_nomad_stop_jobs(names, **options)
325
365
  names.each do |name|
326
- capistrano_nomad_execute_nomad_command(:job, :stop, { namespace: namespace }, name)
366
+ capistrano_nomad_execute_nomad_command(:job, :stop, options, name)
327
367
  end
328
368
  end
329
369
 
330
- def capistrano_nomad_restart_jobs(names, namespace: nil)
370
+ def capistrano_nomad_restart_jobs(names, **options)
331
371
  names.each do |name|
332
- capistrano_nomad_execute_nomad_command(:job, :restart, { namespace: namespace }, name)
372
+ capistrano_nomad_execute_nomad_command(:job, :restart, options, name)
333
373
  end
334
374
  end
335
375
 
@@ -339,6 +379,29 @@ def capistrano_nomad_purge_jobs(names, namespace: nil, is_detached: true)
339
379
  end
340
380
  end
341
381
 
342
- def capistrano_nomad_display_job_status(name, *args)
343
- capistrano_nomad_execute_nomad_command(:status, *args, name)
382
+ def capistrano_nomad_display_job_status(name, **options)
383
+ capistrano_nomad_execute_nomad_command(:status, options, name)
384
+ end
385
+
386
+ def capistrano_nomad_display_job_logs(name, namespace: nil, **options)
387
+ if (task_details = capistrano_nomad_find_job_task_details(name, namespace: namespace, task: ENV["TASK"]))
388
+ capistrano_nomad_execute_nomad_command(
389
+ :alloc,
390
+ :logs,
391
+ options.merge(namespace: namespace, task: task_details[:name]),
392
+ task_details[:alloc_id],
393
+ )
394
+ else
395
+ # If task can't be determined choose a random allocation
396
+ capistrano_nomad_execute_nomad_command(
397
+ :alloc,
398
+ :logs,
399
+ options.merge(namespace: namespace, job: true),
400
+ name,
401
+ )
402
+ end
403
+ end
404
+
405
+ def capistrano_nomad_tail_job_logs(*args, **options)
406
+ capistrano_nomad_display_job_logs(*args, **options.merge(tail: true, n: 50))
344
407
  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.7.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: 2023-11-06 00:00:00.000000000 Z
11
+ date: 2023-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport