Top Level Namespace

Defined Under Namespace

Modules: Capistrano Classes: CapistranoNomadDockerPushImageInteractionHandler, CapistranoNomadErbNamespace

Instance Method Summary collapse

Instance Method Details

#capistrano_nomad_assemble_jobs_docker_images(names, *args) ⇒ Object



393
394
395
396
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 393

def capistrano_nomad_assemble_jobs_docker_images(names, *args)
  capistrano_nomad_build_jobs_docker_images(names, *args)
  capistrano_nomad_push_jobs_docker_images(names, *args)
end

#capistrano_nomad_build_base_job_path(*args) ⇒ Object



55
56
57
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 55

def capistrano_nomad_build_base_job_path(*args)
  capistrano_nomad_build_file_path(fetch(:nomad_jobs_path), *args)
end

#capistrano_nomad_build_base_var_file_path(*args) ⇒ Object



59
60
61
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 59

def capistrano_nomad_build_base_var_file_path(*args)
  capistrano_nomad_build_file_path(fetch(:nomad_var_files_path), *args)
end

#capistrano_nomad_build_docker_image_alias(image_type) ⇒ Object

Raises:

  • (StandardError)


52
53
54
55
56
57
58
59
60
61
62
# File 'lib/capistrano/nomad/helpers/docker.rb', line 52

def capistrano_nomad_build_docker_image_alias(image_type)
  image_alias = fetch(:nomad_docker_image_types).dig(image_type, :alias)
  image_alias = image_alias.call(image_type: image_type) if image_alias&.is_a?(Proc)

  raise StandardError, ":alias not defined for #{image_type}" unless image_alias

  # Add :latest if there's no tag
  image_alias << ":latest" if image_alias.split(":").count == 1

  image_alias
end

#capistrano_nomad_build_docker_image_for_type(image_type) ⇒ Object

Builds docker image from image type

Parameters:

  • image_type (String, Symbol)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/capistrano/nomad/helpers/docker.rb', line 67

def capistrano_nomad_build_docker_image_for_type(image_type)
  image_type = image_type.to_sym
  attributes = fetch(:nomad_docker_image_types)[image_type]
  command = fetch(:nomad_docker_build_command) || "docker build"
  options = Array(fetch(:nomad_docker_build_command_options)) || []

  return unless attributes

  # No need to build if there's no path
  return unless attributes[:path]

  # Ensure images are built for x86_64 which is production env otherwise it will default to local development env which
  # can be arm64 (Apple Silicon)
  options << "--platform linux/amd64"

  if (target = attributes[:target])
    options << "--target #{target}"
  end

  build_args = attributes[:build_args]
  build_args = build_args.call if build_args&.is_a?(Proc)

  (build_args || []).each do |key, value|
    # Escape single quotes so that we can properly pass in build arg values that have spaces and special characters
    # e.g. Don't escape strings (#123) => 'Don'\''t escape strings (#123)'
    value_escaped = value.gsub("'", "\'\\\\'\'")
    options << "--build-arg #{key}='#{value_escaped}'"
  end

  docker_build_command = lambda do |path|
    build_options = options.dup

    [capistrano_nomad_build_docker_image_alias(image_type)]
      .compact
      .each do |tag|
        build_options << "--tag #{tag}"
      end

    "#{command} #{build_options.join(' ')} #{path}"
  end

  case attributes[:strategy]

  # We need to build Docker container locally
  when :local_build, :local_push
    run_locally do
      # If any of these files exist then we're in the middle of rebase so we should interrupt
      if ["rebase-merge", "rebase-apply"].any? { |f| File.exist?("#{capistrano_nomad_git.dir.path}/.git/#{f}") }
        raise StandardError, "Still in the middle of git rebase, interrupting docker image build"
      end

      execute(docker_build_command.call(capistrano_nomad_root.join(attributes[:path])))
    end

  # We need to build Docker container remotely
  when :remote_build, :remote_push
    remote_path = Pathname.new(release_path).join(attributes[:path])
    capistrano_nomad_upload(local_path: attributes[:path], remote_path: remote_path)

    capistrano_nomad_run_remotely do
      execute(docker_build_command.call(remote_path))
    end
  end
end

#capistrano_nomad_build_file_path(parent_path, basename, kind: nil, namespace: :default) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 32

def capistrano_nomad_build_file_path(parent_path, basename, kind: nil, namespace: :default)
  segments = [parent_path]

  unless namespace == :default
    case kind

    # Always upload to namespace folder on remote
    when :release
      segments << namespace

    # Otherwise path can be overriden of where files belonging to namespace are stored locally
    else
      namespace_options = capistrano_nomad_fetch_namespace_options(namespace)

      segments << (namespace_options[:path] || namespace)
    end
  end

  segments << "#{basename}.hcl"

  segments.join("/")
end

#capistrano_nomad_build_jobs_docker_images(names, *args) ⇒ Object



377
378
379
380
381
382
383
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 377

def capistrano_nomad_build_jobs_docker_images(names, *args)
  image_types = capistrano_nomad_fetch_jobs_docker_image_types(names, *args)

  return false if image_types.empty?

  image_types.each { |i| capistrano_nomad_build_docker_image_for_type(i) }
end

#capistrano_nomad_build_local_job_path(name, *args) ⇒ Object



74
75
76
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 74

def capistrano_nomad_build_local_job_path(name, *args)
  capistrano_nomad_build_local_path(capistrano_nomad_build_base_job_path(name, *args))
end

#capistrano_nomad_build_local_path(path) ⇒ Object

Raises:

  • (StandardError)


63
64
65
66
67
68
69
70
71
72
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 63

def capistrano_nomad_build_local_path(path)
  local_path = capistrano_nomad_root.join(path)

  # Determine if it has .erb appended or not
  found_local_path = [local_path, "#{local_path}.erb"].find { |each_local_path| File.exist?(each_local_path) }

  raise StandardError, "Could not find local path: #{path}" unless found_local_path

  found_local_path
end

#capistrano_nomad_build_local_var_file_path(name, *args) ⇒ Object



78
79
80
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 78

def capistrano_nomad_build_local_var_file_path(name, *args)
  capistrano_nomad_build_local_path(capistrano_nomad_build_base_var_file_path(name, *args))
end

#capistrano_nomad_build_release_job_path(name, **options) ⇒ Object



82
83
84
85
86
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 82

def capistrano_nomad_build_release_job_path(name, **options)
  options[:kind] = :release

  "#{release_path}#{capistrano_nomad_ensure_absolute_path(capistrano_nomad_build_base_job_path(name, **options))}"
end

#capistrano_nomad_build_release_var_file_path(name, **options) ⇒ Object



88
89
90
91
92
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 88

def capistrano_nomad_build_release_var_file_path(name, **options)
  options[:kind] = :release

  "#{release_path}#{capistrano_nomad_ensure_absolute_path(capistrano_nomad_build_base_var_file_path(name, **options))}"
end

#capistrano_nomad_capture_nomad_command(*args) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 125

def capistrano_nomad_capture_nomad_command(*args)
  output = nil

  capistrano_nomad_run_remotely do
    output = capistrano_nomad_run_nomad_command(:capture, *args)
  end

  output
end

#capistrano_nomad_deep_symbolize_hash_keys(hash) ⇒ Object



5
6
7
# File 'lib/capistrano/nomad/helpers/base.rb', line 5

def capistrano_nomad_deep_symbolize_hash_keys(hash)
  JSON.parse(JSON[hash], symbolize_names: true)
end

#capistrano_nomad_define_group_tasks(namespace:) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 288

def capistrano_nomad_define_group_tasks(namespace:)
  define_tasks = lambda do |nomad_namespace: nil|
    desc "Build #{nomad_namespace} job Docker images"
    task :build do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_push_jobs_docker_images(names, namespace: jobs_namespace)
      end
    end

    desc "Push #{nomad_namespace} job Docker images"
    task :push do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_push_jobs_docker_images(names, namespace: jobs_namespace)
      end
    end

    desc "Build and push #{nomad_namespace} job Docker images"
    task :assemble do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_assemble_jobs_docker_images(names, namespace: jobs_namespace)
      end
    end

    desc "Upload #{nomad_namespace} jobs"
    task :upload do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_upload_jobs(names, namespace: jobs_namespace)
      end
    end

    desc "Run #{nomad_namespace} jobs"
    task :run do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_run_jobs(names, namespace: jobs_namespace)
      end
    end

    desc "Upload and run #{nomad_namespace} jobs"
    task :upload_run do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_upload_run_jobs(names, namespace: jobs_namespace)
      end
    end

    desc "Deploy #{nomad_namespace} jobs"
    task :deploy do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_deploy_jobs(names, namespace: jobs_namespace)
      end
    end

    desc "Rerun #{nomad_namespace} jobs"
    task :rerun do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_rerun_jobs(names, namespace: jobs_namespace)
      end
    end

    desc "Restart #{nomad_namespace} jobs"
    task :restart do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_restart_jobs(names, namespace: jobs_namespace)
      end
    end

    desc "Stop #{nomad_namespace} jobs"
    task :stop do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_stop_jobs(names, namespace: jobs_namespace)
      end
    end

    desc "Purge #{nomad_namespace} jobs"
    task :purge do
      capistrano_nomad_fetch_jobs_names_by_namespace(namespace: nomad_namespace).each do |jobs_namespace, names|
        capistrano_nomad_purge_jobs(names, namespace: jobs_namespace)
      end
    end
  end

  if namespace
    namespace(namespace) do
      define_tasks.call(nomad_namespace: namespace)
    end
  else
    define_tasks.call
  end
end

#capistrano_nomad_deploy_jobs(names, **options) ⇒ Object



489
490
491
492
493
494
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 489

def capistrano_nomad_deploy_jobs(names, **options)
  general_options = options.slice!(:is_detached)

  capistrano_nomad_assemble_jobs_docker_images(names, **general_options)
  capistrano_nomad_upload_run_jobs(names, **general_options.merge(options))
end

#capistrano_nomad_display_job_logs(name, namespace: :default, **options) ⇒ Object



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 520

def capistrano_nomad_display_job_logs(name, namespace: :default, **options)
  if (task_details = capistrano_nomad_find_job_task_details(name, namespace: namespace, task: ENV["TASK"]))
    capistrano_nomad_execute_nomad_command(
      :alloc,
      :logs,
      options.merge(namespace: namespace, task: task_details[:name]),
      task_details[:alloc_id],
    )
  else
    # If task can't be determined choose a random allocation
    capistrano_nomad_execute_nomad_command(
      :alloc,
      :logs,
      options.merge(namespace: namespace, job: true),
      name,
    )
  end
end

#capistrano_nomad_display_job_status(name, **options) ⇒ Object



516
517
518
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 516

def capistrano_nomad_display_job_status(name, **options)
  capistrano_nomad_execute_nomad_command(:status, options, name)
end

#capistrano_nomad_docker_image_types_manifest_pathObject



17
18
19
# File 'lib/capistrano/nomad/helpers/docker.rb', line 17

def capistrano_nomad_docker_image_types_manifest_path
  shared_path.join("docker-image-types.json")
end

#capistrano_nomad_ensure_absolute_path(path) ⇒ Object



28
29
30
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 28

def capistrano_nomad_ensure_absolute_path(path)
  path[0] == "/" ? path : "/#{path}"
end

#capistrano_nomad_exec_within_job(name, command, namespace: :default, task: nil) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 177

def capistrano_nomad_exec_within_job(name, command, namespace: :default, task: nil)
  capistrano_nomad_run_remotely do
    if (task_details = capistrano_nomad_find_job_task_details(name, namespace: namespace, task: task))
      capistrano_nomad_execute_nomad_command(
        :alloc,
        :exec,
        { namespace: namespace, task: task_details[:name] },
        task_details[:alloc_id],
        command,
      )
    else
      # If alloc can't be determined then choose at random
      capistrano_nomad_execute_nomad_command(
        :alloc,
        :exec,
        { namespace: namespace, job: true },
        task,
        command,
      )
    end
  end
end

#capistrano_nomad_execute_nomad_command(*args) ⇒ Object



117
118
119
120
121
122
123
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 117

def capistrano_nomad_execute_nomad_command(*args)
  capistrano_nomad_run_remotely do |host|
    run_interactively(host) do
      capistrano_nomad_run_nomad_command(:execute, *args)
    end
  end
end

#capistrano_nomad_fetch_job_options(name, *args, namespace: :default) ⇒ Object



253
254
255
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 253

def capistrano_nomad_fetch_job_options(name, *args, namespace: :default)
  fetch(:nomad_jobs).dig(namespace, name.to_sym, *args)
end

#capistrano_nomad_fetch_job_var_files(name, *args) ⇒ Object



257
258
259
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 257

def capistrano_nomad_fetch_job_var_files(name, *args)
  capistrano_nomad_fetch_job_options(name, :var_files, *args) || []
end

#capistrano_nomad_fetch_jobs_docker_image_types(names, namespace: :default) ⇒ Object



284
285
286
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 284

def capistrano_nomad_fetch_jobs_docker_image_types(names, namespace: :default)
  names.map { |n| fetch(:nomad_jobs).dig(namespace, n.to_sym, :docker_image_types) }.flatten.compact.uniq
end

#capistrano_nomad_fetch_jobs_names_by_namespace(namespace: :default) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 261

def capistrano_nomad_fetch_jobs_names_by_namespace(namespace: :default)
  # Can pass tags via command line (e.g. TAG=foo or TAGS=foo,bar)
  tags =
    [ENV["TAG"], ENV["TAGS"]].map do |tag_args|
      next unless tag_args.presence

      tag_args.split(",").map(&:presence).compact.map(&:to_sym)
    end
      .flatten
      .compact

  fetch(:nomad_jobs).each_with_object({}) do |(jobs_namespace, jobs_options), hash|
    next if !namespace.nil? && namespace != jobs_namespace

    hash[jobs_namespace] = jobs_options.each_with_object([]) do |(job_name, job_options), collection|
      # Filter jobs by tags if specified
      next if tags.any? && ((job_options[:tags]&.map(&:to_sym) || []) & tags).empty?

      collection << job_name
    end
  end
end

#capistrano_nomad_fetch_namespace_options(namespace) ⇒ Object



249
250
251
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 249

def capistrano_nomad_fetch_namespace_options(namespace)
  fetch(:nomad_namespaces)&.dig(namespace)
end

#capistrano_nomad_find_job_task_details(name, namespace: :default, task: nil) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 135

def capistrano_nomad_find_job_task_details(name, namespace: :default, task: nil)
  task = task.presence || name

  # Find alloc id that contains task that is also running
  allocs_output = capistrano_nomad_capture_nomad_command(
    :job,
    :allocs,
    { namespace: namespace, t: "'{{range .}}{{ .ID }},{{ .ClientStatus }},{{ .TaskGroup }}|{{end}}'" },
    name,
  )
  alloc_id = allocs_output
    .split("|")
    .map { |s| s.split(",") }
    .find { |_, s, t| s == "running" && t == task.to_s }
    &.first

  # Can't continue if we can't choose an alloc id
  return unless alloc_id

  tasks_output = capistrano_nomad_capture_nomad_command(
    :alloc,
    :status,
    { namespace: namespace, t: "'{{range $key, $value := .TaskStates}}{{ $key }},{{ .State }}|{{end}}'" },
    alloc_id,
  )
  tasks_by_score = tasks_output.split("|").each_with_object({}) do |task_output, hash|
    task, state = task_output.split(",")

    score = 0
    score += 5 if state == "running"
    score += 5 unless task.match?(/connect-proxy/)

    hash[task] = score
  end
  task = tasks_by_score.max_by { |_, v| v }.first

  {
    alloc_id: alloc_id,
    name: task,
  }
end

#capistrano_nomad_gitObject



3
4
5
# File 'lib/capistrano/nomad/helpers/git.rb', line 3

def capistrano_nomad_git
  @capistrano_nomad_git ||= Git.open(".")
end

#capistrano_nomad_git_commitObject



7
8
9
# File 'lib/capistrano/nomad/helpers/git.rb', line 7

def capistrano_nomad_git_commit
  @capistrano_nomad_git_commit ||= capistrano_nomad_git.log.first
end

#capistrano_nomad_git_commit_idObject



11
12
13
# File 'lib/capistrano/nomad/helpers/git.rb', line 11

def capistrano_nomad_git_commit_id
  @capistrano_nomad_git_commit_id ||= capistrano_nomad_git_commit.sha
end

#capistrano_nomad_open_job_ui(name, namespace: :default) ⇒ Object



543
544
545
546
547
548
549
550
551
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 543

def capistrano_nomad_open_job_ui(name, namespace: :default)
  run_locally do
    url = "#{fetch(:nomad_ui_url)}/ui/jobs/#{name}"
    url += "@#{namespace}" if namespace

    # Only macOS supported for now
    execute(:open, url)
  end
end

#capistrano_nomad_plan_jobs(names, *args) ⇒ Object



428
429
430
431
432
433
434
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 428

def capistrano_nomad_plan_jobs(names, *args)
  names.each do |name|
    args = [capistrano_nomad_build_release_job_path(name, *args)]

    capistrano_nomad_execute_nomad_command(:plan, *args)
  end
end

#capistrano_nomad_purge_jobs(names, namespace: :default, is_detached: true) ⇒ Object



510
511
512
513
514
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 510

def capistrano_nomad_purge_jobs(names, namespace: :default, is_detached: true)
  names.each do |name|
    capistrano_nomad_execute_nomad_command(:stop, { namespace: namespace, purge: true, detach: is_detached }, name)
  end
end

#capistrano_nomad_push_docker_image_for_type(image_type, is_manifest_updated: true) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/capistrano/nomad/helpers/docker.rb', line 132

def capistrano_nomad_push_docker_image_for_type(image_type, is_manifest_updated: true)
  attributes = fetch(:nomad_docker_image_types)[image_type]
  alias_digest = attributes&.dig(:alias_digest)

  return false unless [:local_push, :remote_push].include?(attributes[:strategy])

  run_locally do
    # Only push Docker image if it was built from path
    if attributes[:path]
      interaction_handler = CapistranoNomadDockerPushImageInteractionHandler.new
      image_alias = capistrano_nomad_build_docker_image_alias(image_type)

      # We should not proceed if image cannot be pushed
      unless execute("docker push #{image_alias}", interaction_handler: interaction_handler)
        raise StandardError, "Docker image push unsuccessful!"
      end

      return unless is_manifest_updated

      # Has the @sha256:xxxx appended so we have the ability to also target by digest
      alias_digest = "#{image_alias}@#{interaction_handler.last_digest}"
    end

    # Update image type manifest
    capistrano_nomad_update_docker_image_types_manifest(image_type,
      alias: image_alias,
      alias_digest: alias_digest,
    )
  end
end

#capistrano_nomad_push_jobs_docker_images(names, *args) ⇒ Object



385
386
387
388
389
390
391
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 385

def capistrano_nomad_push_jobs_docker_images(names, *args)
  image_types = capistrano_nomad_fetch_jobs_docker_image_types(names, *args)

  return false if image_types.empty?

  image_types.each { |i| capistrano_nomad_push_docker_image_for_type(i) }
end

#capistrano_nomad_read_docker_image_types_manifestObject



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/capistrano/nomad/helpers/docker.rb', line 21

def capistrano_nomad_read_docker_image_types_manifest
  manifest = {}

  capistrano_nomad_run_remotely do
    # Ensure file exists
    execute("mkdir", "-p", shared_path)
    execute("touch", capistrano_nomad_docker_image_types_manifest_path)

    output = capture("cat #{capistrano_nomad_docker_image_types_manifest_path}")

    unless output.blank?
      manifest = JSON.parse(output)
    end
  end

  capistrano_nomad_deep_symbolize_hash_keys(manifest)
end

#capistrano_nomad_rerun_jobs(names, **options) ⇒ Object

Remove job and run again



459
460
461
462
463
464
465
466
467
468
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 459

def capistrano_nomad_rerun_jobs(names, **options)
  general_options = options.slice!(:is_detached)

  names.each do |name|
    # Wait for jobs to be purged before running again
    capistrano_nomad_purge_jobs([name], **general_options.merge(is_detached: false))

    capistrano_nomad_run_jobs([name], **general_options.merge(options))
  end
end

#capistrano_nomad_restart_jobs(names, **options) ⇒ Object



496
497
498
499
500
501
502
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 496

def capistrano_nomad_restart_jobs(names, **options)
  names.each do |name|
    # Automatic yes to prompts. If set, the command automatically restarts multi-region jobs only in the region targeted
    # by the command, ignores batch errors, and automatically proceeds with the remaining batches without waiting
    capistrano_nomad_execute_nomad_command(:job, :restart, options.reverse_merge(yes: true), name)
  end
end

#capistrano_nomad_rootObject



1
2
3
# File 'lib/capistrano/nomad/helpers/base.rb', line 1

def capistrano_nomad_root
  @capistrano_nomad_root ||= Pathname.new(fetch(:root) || "")
end

#capistrano_nomad_run_jobs(names, namespace: :default, is_detached: true) ⇒ Object



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 436

def capistrano_nomad_run_jobs(names, namespace: :default, is_detached: true)
  names.each do |name|
    run_options = {
      namespace: namespace,
      detach: is_detached,

      # Don't reset counts since they may have been scaled
      preserve_counts: true,
    }

    capistrano_nomad_fetch_job_var_files(name, namespace: namespace).each do |var_file|
      run_options[:var_file] = capistrano_nomad_build_release_var_file_path(var_file, namespace: namespace)
    end

    capistrano_nomad_execute_nomad_command(
      :run,
      run_options,
      capistrano_nomad_build_release_job_path(name, namespace: namespace),
    )
  end
end

#capistrano_nomad_run_nomad_command(kind, *args) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 94

def capistrano_nomad_run_nomad_command(kind, *args)
  converted_args = args.each_with_object([]) do |arg, collection|
    # If hash then convert it as options
    if arg.is_a?(Hash)
      arg.each do |key, value|
        next unless value

        option = "-#{key.to_s.dasherize}"

        # Doesn't need a value if it's just meant to be a flag
        option << "=#{value}" unless value == true

        collection << option
      end
    else
      collection << arg
    end
  end

  # Ignore errors
  public_send(kind, :nomad, *converted_args, raise_on_non_zero_exit: false)
end

#capistrano_nomad_run_remotely(&block) ⇒ Object



9
10
11
# File 'lib/capistrano/nomad/helpers/base.rb', line 9

def capistrano_nomad_run_remotely(&block)
  on(roles(:manager), &block)
end

#capistrano_nomad_stop_jobs(names, **options) ⇒ Object



504
505
506
507
508
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 504

def capistrano_nomad_stop_jobs(names, **options)
  names.each do |name|
    capistrano_nomad_execute_nomad_command(:job, :stop, options, name)
  end
end

#capistrano_nomad_tail_job_logs(*args, **options) ⇒ Object



539
540
541
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 539

def capistrano_nomad_tail_job_logs(*args, **options)
  capistrano_nomad_display_job_logs(*args, **options.merge(tail: true, n: 50))
end

#capistrano_nomad_update_docker_image_types_manifest(image_type, properties = {}) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/capistrano/nomad/helpers/docker.rb', line 39

def capistrano_nomad_update_docker_image_types_manifest(image_type, properties = {})
  capistrano_nomad_run_remotely do
    # Read and update manifest
    manifest = capistrano_nomad_read_docker_image_types_manifest
    manifest[image_type] = (manifest[image_type] || {}).merge(properties.stringify_keys)

    io = StringIO.new(JSON.pretty_generate(manifest))

    # Write to manifest
    upload!(io, capistrano_nomad_docker_image_types_manifest_path)
  end
end

#capistrano_nomad_upload(local_path:, remote_path:, erb_vars: {}) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 200

def capistrano_nomad_upload(local_path:, remote_path:, erb_vars: {})
  # If directory upload everything within the directory
  if File.directory?(local_path)
    Dir.glob("#{local_path}/*").each do |path|
      capistrano_nomad_upload(local_path: path, remote_path: "#{remote_path}/#{File.basename(path)}")
    end

  # If file, attempt to always parse it as ERB
  else
    docker_image_types = fetch(:nomad_docker_image_types)
    docker_image_types_manifest = capistrano_nomad_read_docker_image_types_manifest

    # Merge manifest into image types
    docker_image_types_manifest.each do |manifest_image_type, manifest_attributes|
      docker_image_types[manifest_image_type]&.merge!(manifest_attributes) || {}
    end

    # Parse manifest files using ERB
    erb = ERB.new(File.open(local_path).read, trim_mode: "-")

    final_erb_vars = {
      git_commit_id: fetch(:current_revision) || capistrano_nomad_git_commit_id,
      docker_image_types: docker_image_types,
    }

    # Add global ERB vars
    final_erb_vars.merge!(fetch(:nomad_template_vars) || {})

    # Add job-specific ERB vars
    final_erb_vars.merge!(erb_vars)

    # We use a custom namespace class so that we can include helper methods into the namespace to make them available
    # for template to access
    namespace = CapistranoNomadErbNamespace.new(
      context: self,
      vars: final_erb_vars,
    )

    string_io = StringIO.new(erb.result(namespace.instance_eval { binding }))

    capistrano_nomad_run_remotely do
      # Ensure parent directory exists
      execute(:mkdir, "-p", File.dirname(remote_path))

      upload!(string_io, remote_path)
    end
  end
end

#capistrano_nomad_upload_jobs(names, *args) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 398

def capistrano_nomad_upload_jobs(names, *args)
  # Var files can be shared between jobs so don't upload duplicates
  uniq_var_files = names.map { |n| capistrano_nomad_fetch_job_var_files(n, *args) }.flatten.uniq

  uniq_var_files.each do |var_file|
    capistrano_nomad_upload(
      local_path: capistrano_nomad_build_local_var_file_path(var_file, *args),
      remote_path: capistrano_nomad_build_release_var_file_path(var_file, *args),
    )
  end

  run_locally do
    names.each do |name|
      nomad_job_options = capistrano_nomad_fetch_job_options(name, *args)

      # Can set job-specific ERB vars
      erb_vars = nomad_job_options[:erb_vars] || {}

      # Can set a custom template instead
      file_basename = nomad_job_options[:template] || name

      capistrano_nomad_upload(
        local_path: capistrano_nomad_build_local_job_path(file_basename, *args),
        remote_path: capistrano_nomad_build_release_job_path(name, *args),
        erb_vars: erb_vars,
      )
    end
  end
end

#capistrano_nomad_upload_plan_jobs(names, **options) ⇒ Object



470
471
472
473
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 470

def capistrano_nomad_upload_plan_jobs(names, **options)
  capistrano_nomad_upload_jobs(names, **options)
  capistrano_nomad_plan_jobs(names, **options)
end

#capistrano_nomad_upload_rerun_jobs(names, **options) ⇒ Object



482
483
484
485
486
487
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 482

def capistrano_nomad_upload_rerun_jobs(names, **options)
  general_options = options.slice!(:is_detached)

  capistrano_nomad_upload_jobs(names, **general_options)
  capistrano_nomad_rerun_jobs(names, **general_options.merge(options))
end

#capistrano_nomad_upload_run_jobs(names, **options) ⇒ Object



475
476
477
478
479
480
# File 'lib/capistrano/nomad/helpers/nomad.rb', line 475

def capistrano_nomad_upload_run_jobs(names, **options)
  general_options = options.slice!(:is_detached)

  capistrano_nomad_upload_jobs(names, **general_options)
  capistrano_nomad_run_jobs(names, **general_options.merge(options))
end

#nomad_docker_image_type(image_type, attributes = {}) ⇒ Object

Raises:

  • (ArgumentError)


3
4
5
6
7
8
9
10
11
12
13
14
15
16
# File 'lib/capistrano/nomad/helpers/dsl.rb', line 3

def nomad_docker_image_type(image_type, attributes = {})
  docker_image_types = fetch(:nomad_docker_image_types) || {}
  docker_image_types[image_type] = attributes.reverse_merge(
    # By default build and push Docker image locally
    strategy: :local_push,
  )

  raise ArgumentError, "passing in alias_digest is not allowed!" if attributes[:alias_digest]

  # If Docker image doesn't get pushed, this will still be populated
  docker_image_types[image_type][:alias_digest] = attributes[:alias]

  set(:nomad_docker_image_types, docker_image_types)
end

#nomad_job(name, attributes = {}) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/capistrano/nomad/helpers/dsl.rb', line 40

def nomad_job(name, attributes = {})
  # This is the namespace when there's no namespace defined in Nomad too
  @nomad_namespace ||= :default

  attributes[:tags] ||= []

  # Tags added to namespace should be added to all jobs within
  if (nomad_namespace_options = capistrano_nomad_fetch_namespace_options(@nomad_namespace))
    attributes[:tags] += nomad_namespace_options[:tags] || []
  end

  nomad_jobs = fetch(:nomad_jobs) || Hash.new { |h, n| h[n] = {} }
  nomad_jobs[@nomad_namespace][name] = attributes

  set(:nomad_jobs, nomad_jobs)

  define_tasks = lambda do |namespace: nil|
    description_name = ""
    description_name << "#{namespace}/" if namespace != :default
    description_name << name.to_s

    namespace(name) do
      desc "Build #{description_name} job Docker images"
      task :build do
        capistrano_nomad_build_jobs_docker_images([name], namespace: namespace)
      end

      desc "Push #{description_name} job Docker images"
      task :push do
        capistrano_nomad_push_jobs_docker_images([name], namespace: namespace)
      end

      desc "Build and push #{description_name} job Docker images"
      task :assemble do
        capistrano_nomad_build_jobs_docker_images([name], namespace: namespace)
        capistrano_nomad_push_jobs_docker_images([name], namespace: namespace)
      end

      desc "Upload #{description_name} job and related files"
      task :upload do
        capistrano_nomad_upload_jobs([name], namespace: namespace)
      end

      desc "Run #{description_name} job"
      task :run do
        capistrano_nomad_run_jobs([name], namespace: namespace, is_detached: false)
      end

      desc "Purge and run #{description_name} job again"
      task :rerun do
        capistrano_nomad_rerun_jobs([name], namespace: namespace, is_detached: false)
      end

      desc "Upload and plan #{description_name} job"
      task :upload_plan do
        capistrano_nomad_upload_plan_jobs([name], namespace: namespace)
      end

      desc "Upload and run #{description_name} job"
      task :upload_run do
        capistrano_nomad_upload_run_jobs([name], namespace: namespace, is_detached: false)
      end

      desc "Upload and re-run #{description_name} job"
      task :upload_rerun do
        capistrano_nomad_upload_rerun_jobs([name], namespace: namespace, is_detached: false)
      end

      desc "Deploy #{description_name} job"
      task :deploy do
        capistrano_nomad_deploy_jobs([name], namespace: namespace, is_detached: false)
      end

      desc "Stop #{description_name} job"
      task :stop do
        capistrano_nomad_stop_jobs([name], namespace: namespace)
      end

      desc "Restart #{description_name} job"
      task :restart do
        capistrano_nomad_restart_jobs([name], namespace: namespace)
      end

      desc "Purge #{description_name} job"
      task :purge do
        capistrano_nomad_purge_jobs([name], namespace: namespace, is_detached: false)
      end

      desc "Display status of #{description_name} job"
      task :status do
        capistrano_nomad_display_job_status(name, namespace: namespace)
      end

      desc "Open console to #{description_name} job. Specify task with TASK, command with CMD"
      task :console do
        command = ENV["CMD"].presence || "/bin/sh"

        capistrano_nomad_exec_within_job(name, command, namespace: namespace, task: ENV["TASK"])
      end

      desc "Display stdout and stderr of #{description_name} job. Specify task with TASK"
      task :logs do
        capistrano_nomad_tail_job_logs(name, namespace: namespace, stdout: true)
        capistrano_nomad_tail_job_logs(name, namespace: namespace, stderr: true)
      end

      desc "Display stdout of #{description_name} job. Specify task with TASK"
      task :stdout do
        capistrano_nomad_tail_job_logs(name, namespace: namespace, stdout: true)
      end

      desc "Display stderr of #{description_name} job. Specify task with TASK"
      task :stderr do
        capistrano_nomad_tail_job_logs(name, namespace: namespace, stderr: true)
      end

      desc "Follow logs of #{description_name} job. Specify task with TASK"
      task :follow do
        capistrano_nomad_display_job_logs(name, namespace: namespace, f: true)
      end

      desc "Open job in web UI"
      task :ui do
        capistrano_nomad_open_job_ui(name, namespace: namespace)
      end
    end
  end

  namespace(:nomad) do
    if @nomad_namespace
      # Also define tasks without namespace for default Nomad namespace
      define_tasks.call(namespace: @nomad_namespace) if @nomad_namespace == :default

      namespace(@nomad_namespace) do
        define_tasks.call(namespace: @nomad_namespace)
      end
    else
      define_tasks.call
    end
  end
end

#nomad_namespace(namespace, **options, &block) ⇒ Object

Raises:

  • (ArgumentError)


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/capistrano/nomad/helpers/dsl.rb', line 18

def nomad_namespace(namespace, **options, &block)
  raise ArgumentError, "cannot define default nomad namespace" if namespace == :default

  nomad_namespaces = fetch(:nomad_namespaces) || {}
  nomad_namespaces[namespace] = options
  set(:nomad_namespaces, nomad_namespaces)

  # Make namespace active for block
  @nomad_namespace = namespace

  instance_eval(&block)

  @nomad_namespace = nil

  # Define tasks for namespace jobs
  namespace(:nomad) do
    capistrano_nomad_define_group_tasks(namespace: namespace)
  end

  true
end

#nomad_template_helpers(&block) ⇒ Object



182
183
184
# File 'lib/capistrano/nomad/helpers/dsl.rb', line 182

def nomad_template_helpers(&block)
  CapistranoNomadErbNamespace.class_eval(&block)
end