capistrano-nomad 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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