Class: Kitsune::Kit::StateStore

Inherits:
Kitsune::Kit::StateStores::Store show all
Defined in:
lib/kitsune/kit/state_store.rb

Constant Summary collapse

SCHEMA_VERSION =
1

Instance Method Summary collapse

Constructor Details

#initialize(root: Dir.pwd) ⇒ StateStore

Returns a new instance of StateStore.



16
17
18
19
# File 'lib/kitsune/kit/state_store.rb', line 16

def initialize(root: Dir.pwd)
  super()
  @directory = Pathname(root).expand_path.join(".kitsune/state")
end

Instance Method Details

#delete(environment) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/kitsune/kit/state_store.rb', line 53

def delete(environment)
  with_lock(environment) do
    next unless state_path(environment).file?

    state_path(environment).delete
    true
  end
end

#read(environment) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/kitsune/kit/state_store.rb', line 21

def read(environment)
  path = state_path(environment)
  return empty_state(environment) unless path.file?

  with_lock(environment, shared: true) do
    parsed = JSON.parse(path.read)
    validate!(parsed, environment)
    parsed
  end
rescue JSON::ParserError => e
  raise Errors::ConfigurationError.new(
    "state file is invalid JSON: #{path}",
    hint: "Restore the state backup or import the environment again.",
    context: { cause: e.message }
  )
end

#update(environment) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/kitsune/kit/state_store.rb', line 38

def update(environment)
  with_lock(environment) do
    current = read_without_lock(environment)
    updated = yield(deep_copy(current))
    updated["updated_at"] = Time.now.utc.iso8601(6)
    validate!(updated, environment)
    write_atomically(state_path(environment), JSON.pretty_generate(updated) << "\n")
    deep_copy(updated)
  end
end

#with_execution_lock(environment) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/kitsune/kit/state_store.rb', line 62

def with_execution_lock(environment)
  FileUtils.mkdir_p(@directory, mode: 0o700)
  path = @directory.join("#{safe_environment(environment)}.operation.lock")
  File.open(path, File::RDWR | File::CREAT, 0o600) do |file|
    locked = file.flock(File::LOCK_EX | File::LOCK_NB)
    unless locked
      raise Errors::UnsafeOperationError.new(
        "another mutating operation is active for #{environment}",
        hint: "Wait for it to finish or stop it safely before retrying."
      )
    end
    yield
  ensure
    file.flock(File::LOCK_UN) if locked
  end
end

#write(environment, state) ⇒ Object



49
50
51
# File 'lib/kitsune/kit/state_store.rb', line 49

def write(environment, state)
  update(environment) { state }
end