Welcome to mirror list, hosted at ThFree Co, Russian Federation.

remote_state_handler.rb « terraform « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 7e79cb9e0075b44ae166cfc8aca1b79b1563956a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
# frozen_string_literal: true

module Terraform
  class RemoteStateHandler < BaseService
    include Gitlab::OptimisticLocking

    StateLockedError = Class.new(StandardError)
    UnauthorizedError = Class.new(StandardError)

    def find_with_lock
      retrieve_with_lock(find_only: true) do |state|
        yield state if block_given?
      end
    end

    def handle_with_lock
      raise UnauthorizedError unless can_modify_state?

      retrieve_with_lock do |state|
        raise StateLockedError unless lock_matches?(state)

        yield state if block_given?

        state.save! unless state.destroyed?
      end

      nil
    end

    def lock!
      raise ArgumentError if params[:lock_id].blank?
      raise UnauthorizedError unless can_modify_state?

      retrieve_with_lock do |state|
        raise StateLockedError if state.locked?

        state.lock_xid = params[:lock_id]
        state.locked_by_user = current_user
        state.locked_at = Time.current

        state.save!
      end
    end

    def unlock!
      raise UnauthorizedError unless can_modify_state?

      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

    def retrieve_with_lock(find_only: false)
      create_or_find!(find_only: find_only).tap { |state| retry_optimistic_lock(state) { |state| yield state } }
    end

    def create_or_find!(find_only:)
      raise ArgumentError unless params[:name].present?

      find_params = { project: project, name: params[:name] }

      if find_only
        Terraform::State.find_by(find_params) || # rubocop: disable CodeReuse/ActiveRecord
          raise(ActiveRecord::RecordNotFound.new("Couldn't find state"))
      else
        Terraform::State.create_or_find_by(find_params)
      end
    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

    def can_modify_state?
      current_user.can?(:admin_terraform_state, project)
    end
  end
end