Class: Harbor::DeployLock

Inherits:
Object
  • Object
show all
Defined in:
lib/harbor/deploy_lock.rb

Instance Method Summary collapse

Constructor Details

#initialize(lock_dir: nil, timeout: nil) ⇒ DeployLock

Returns a new instance of DeployLock.



9
10
11
12
13
# File 'lib/harbor/deploy_lock.rb', line 9

def initialize(lock_dir: nil, timeout: nil)
  @lock_dir = lock_dir || DEFAULT_LOCK_DIR
  @timeout = timeout || 1800 # 30 minutes default
  FileUtils.mkdir_p(@lock_dir)
end

Instance Method Details

#acquire!(project:, destination: nil, initiator: "cli") ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/harbor/deploy_lock.rb', line 15

def acquire!(project:, destination: nil, initiator: "cli")
  lock_file = lock_path(project, destination)

  if locked?(project, destination)
    info = lock_info(project, destination)
    raise LockConflict,
          "Deploy lock held for #{project}" \
          "#{destination ? ":#{destination}" : ""} by #{info['initiator']} " \
          "since #{info['timestamp']} (PID: #{info['pid']}). " \
          "Lock expires in #{remaining_seconds(info)}s."
  end

  data = {
    pid: Process.pid,
    timestamp: Time.now.utc.iso8601,
    initiator: initiator,
    project: project,
    destination: destination
  }
  File.write(lock_file, JSON.generate(data))
end

#lock_info(project, destination = nil) ⇒ Object



79
80
81
82
83
84
# File 'lib/harbor/deploy_lock.rb', line 79

def lock_info(project, destination = nil)
  lock_file = lock_path(project, destination)
  return nil unless File.exist?(lock_file)

  JSON.parse(File.read(lock_file))
end

#locked?(project, destination = nil) ⇒ Boolean

Returns:

  • (Boolean)


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
# File 'lib/harbor/deploy_lock.rb', line 51

def locked?(project, destination = nil)
  lock_file = lock_path(project, destination)
  return false unless File.exist?(lock_file)

  data = JSON.parse(File.read(lock_file))
  lock_time = Time.parse(data["timestamp"])

  if Time.now.utc - lock_time > @timeout
    File.delete(lock_file)
    return false
  end

  # Check if PID is still alive
  if data["pid"]
    begin
      Process.kill(0, data["pid"])
    rescue Errno::ESRCH
      # Process is dead, stale lock
      File.delete(lock_file)
      return false
    rescue Errno::EPERM
      # Process exists but we can't signal it — lock is valid
    end
  end

  true
end

#refresh(project:, destination: nil) ⇒ Object



42
43
44
45
46
47
48
49
# File 'lib/harbor/deploy_lock.rb', line 42

def refresh(project:, destination: nil)
  lock_file = lock_path(project, destination)
  return unless File.exist?(lock_file)

  data = JSON.parse(File.read(lock_file))
  data["timestamp"] = Time.now.utc.iso8601
  File.write(lock_file, JSON.generate(data))
end

#release(project:, destination: nil) ⇒ Object



37
38
39
40
# File 'lib/harbor/deploy_lock.rb', line 37

def release(project:, destination: nil)
  lock_file = lock_path(project, destination)
  File.delete(lock_file) if File.exist?(lock_file)
end