summaryrefslogtreecommitdiff
path: root/app/services/environments/schedule_to_delete_review_apps_service.rb
blob: b3b86689748d8dd3997ce27974ff8b57a2cd06e6 (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
91
92
93
94
95
96
97
98
99
100
101
102
# frozen_string_literal: true

module Environments
  class ScheduleToDeleteReviewAppsService < ::BaseService
    include ::Gitlab::ExclusiveLeaseHelpers

    EXCLUSIVE_LOCK_KEY_BASE = 'environments:delete_review_apps:lock'
    LOCK_TIMEOUT = 2.minutes

    def execute
      if validation_error = validate
        return validation_error
      end

      mark_deletable_environments
    end

    private

    def key
      "#{EXCLUSIVE_LOCK_KEY_BASE}:#{project.id}"
    end

    def dry_run?
      return true if params[:dry_run].nil?

      params[:dry_run]
    end

    def validate
      return if can?(current_user, :destroy_environment, project)

      Result.new(error_message: "You do not have permission to destroy environments in this project", status: :unauthorized)
    end

    def mark_deletable_environments
      in_lock(key, ttl: LOCK_TIMEOUT, retries: 1) do
        unsafe_mark_deletable_environments
      end

    rescue FailedToObtainLockError
      Result.new(error_message: "Another process is already processing a delete request. Please retry later.", status: :conflict)
    end

    def unsafe_mark_deletable_environments
      result = Result.new
      environments = project.environments
                            .not_scheduled_for_deletion
                            .stopped_review_apps(params[:before], params[:limit])

      # Check if the actor has write permission to a potentially-protected environment.
      deletable, failed = *environments.partition { |env| current_user.can?(:destroy_environment, env) }

      if deletable.any? && failed.empty?
        mark_for_deletion(deletable) unless dry_run?
        result.set_status(:ok)
        result.set_scheduled_entries(deletable)
      else
        result.set_status(
          :bad_request,
          error_message: "Failed to authorize deletions for some or all of the environments. Ask someone with more permissions to delete the environments."
        )

        result.set_unprocessable_entries(failed)
      end

      result
    end

    def mark_for_deletion(deletable_environments)
      Environment.for_id(deletable_environments).schedule_to_delete
    end

    class Result
      attr_accessor :scheduled_entries, :unprocessable_entries, :error_message, :status

      def initialize(scheduled_entries: [], unprocessable_entries: [], error_message: nil, status: nil)
        self.scheduled_entries = scheduled_entries
        self.unprocessable_entries = unprocessable_entries
        self.error_message = error_message
        self.status = status
      end

      def success?
        status == :ok
      end

      def set_status(status, error_message: nil)
        self.status = status
        self.error_message = error_message
      end

      def set_scheduled_entries(entries)
        self.scheduled_entries = entries
      end

      def set_unprocessable_entries(entries)
        self.unprocessable_entries = entries
      end
    end
  end
end