2020-04-22 19:07:51 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Terraform
|
|
|
|
class RemoteStateHandler < BaseService
|
2023-06-20 00:43:36 +05:30
|
|
|
include Gitlab::OptimisticLocking
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
StateLockedError = Class.new(StandardError)
|
2022-07-23 23:45:48 +05:30
|
|
|
StateDeletedError = Class.new(StandardError)
|
2020-07-28 23:09:34 +05:30
|
|
|
UnauthorizedError = Class.new(StandardError)
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
def find_with_lock
|
2020-07-28 23:09:34 +05:30
|
|
|
retrieve_with_lock(find_only: true) do |state|
|
|
|
|
yield state if block_given?
|
|
|
|
end
|
2020-04-22 19:07:51 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def handle_with_lock
|
2020-07-28 23:09:34 +05:30
|
|
|
raise UnauthorizedError unless can_modify_state?
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
retrieve_with_lock do |state|
|
|
|
|
raise StateLockedError unless lock_matches?(state)
|
|
|
|
|
|
|
|
yield state if block_given?
|
|
|
|
|
|
|
|
state.save! unless state.destroyed?
|
|
|
|
end
|
2020-11-05 12:06:23 +05:30
|
|
|
|
|
|
|
nil
|
2020-04-22 19:07:51 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def lock!
|
|
|
|
raise ArgumentError if params[:lock_id].blank?
|
2020-07-28 23:09:34 +05:30
|
|
|
raise UnauthorizedError unless can_modify_state?
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
retrieve_with_lock do |state|
|
|
|
|
raise StateLockedError if state.locked?
|
|
|
|
|
|
|
|
state.lock_xid = params[:lock_id]
|
|
|
|
state.locked_by_user = current_user
|
2020-05-24 23:13:21 +05:30
|
|
|
state.locked_at = Time.current
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
state.save!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def unlock!
|
2020-07-28 23:09:34 +05:30
|
|
|
raise UnauthorizedError unless can_modify_state?
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
retrieve_with_lock do |state|
|
|
|
|
# force-unlock does not pass ID, so we ignore it if it is missing
|
|
|
|
raise StateLockedError unless params[:lock_id].nil? || lock_matches?(state)
|
|
|
|
|
|
|
|
state.lock_xid = nil
|
|
|
|
state.locked_by_user = nil
|
|
|
|
state.locked_at = nil
|
|
|
|
|
|
|
|
state.save!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
def retrieve_with_lock(find_only: false)
|
2023-06-20 00:43:36 +05:30
|
|
|
create_or_find!(find_only: find_only).tap do |state|
|
|
|
|
retry_lock(state, name: "Terraform state: #{state.id}") { yield state }
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def create_or_find!(find_only:)
|
|
|
|
raise ArgumentError unless params[:name].present?
|
|
|
|
|
|
|
|
find_params = { project: project, name: params[:name] }
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
state = if find_only
|
|
|
|
find_state!(find_params)
|
|
|
|
else
|
2023-06-20 00:43:36 +05:30
|
|
|
Terraform::State.safe_find_or_create_by(find_params)
|
2022-07-23 23:45:48 +05:30
|
|
|
end
|
2021-03-11 19:13:27 +05:30
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
raise StateDeletedError if state.deleted_at?
|
2021-03-11 19:13:27 +05:30
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
state
|
2020-04-22 19:07:51 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def lock_matches?(state)
|
|
|
|
return true if state.lock_xid.nil? && params[:lock_id].nil?
|
|
|
|
|
|
|
|
ActiveSupport::SecurityUtils
|
|
|
|
.secure_compare(state.lock_xid.to_s, params[:lock_id].to_s)
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
def can_modify_state?
|
|
|
|
current_user.can?(:admin_terraform_state, project)
|
|
|
|
end
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
def find_state(find_params)
|
|
|
|
Terraform::State.find_by(find_params) # rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_state!(find_params)
|
2021-06-08 01:23:25 +05:30
|
|
|
find_state(find_params) || raise(ActiveRecord::RecordNotFound, "Couldn't find state")
|
2021-03-11 19:13:27 +05:30
|
|
|
end
|
2020-04-22 19:07:51 +05:30
|
|
|
end
|
|
|
|
end
|