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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
# frozen_string_literal: true
# This script deletes all subgroups of a group specified by ENV['TOP_LEVEL_GROUP_NAME']
#
# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
# Optional environment variable: TOP_LEVEL_GROUP_NAME (defaults to 'gitlab-qa-sandbox-group-<current weekday #>')
# Optional environment variable: CLEANUP_ALL_QA_SANDBOX_GROUPS (defaults to false)
# Set CLEANUP_ALL_QA_SANDBOX_GROUPS to true if you would like to delete all subgroups under all
# 'gitlab-qa-sandbox-group-*' groups. Otherwise, this will fall back to TOP_LEVEL_GROUP_NAME.
# Optional environment variable: PERMANENTLY_DELETE (defaults to false)
# Set PERMANENTLY_DELETE to true if you would like to permanently delete subgroups on an environment with
# deletion protection enabled. Otherwise, subgroups will remain available during the retention period specified
# in admin settings. On environments with deletion protection disabled, subgroups will always be permanently deleted.
#
# Run `rake delete_subgroups`
module QA
module Tools
class DeleteSubgroups
include Support::API
include Ci::Helpers
include Lib::Group
def initialize(delete_before: (Date.today - 3).to_s)
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
@api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
@failed_deletion_attempts = []
@delete_before = Date.parse(delete_before)
end
def run
if ENV['CLEANUP_ALL_QA_SANDBOX_GROUPS']
(1..7).each do |group_number|
group_id = fetch_group_id(@api_client, group_number)
delete_subgroups(group_id)
end
else
group_id = fetch_group_id(@api_client)
delete_subgroups(group_id)
end
end
private
def delete_subgroups(group_id)
return unless group_id
subgroups = fetch_subgroups(group_id)
return logger.info('No subgroups available') if subgroups.empty?
subgroups_marked_for_deletion = mark_for_deletion(subgroups)
if ENV['PERMANENTLY_DELETE'] && !subgroups_marked_for_deletion.empty?
delete_permanently(subgroups_marked_for_deletion)
end
print_failed_deletion_attempts
logger.info('Done')
end
def fetch_subgroups(group_id)
logger.info("Fetching subgroups...")
api_path = "/groups/#{group_id}/subgroups"
page_no = '1'
subgroups = []
while page_no.present?
subgroups_response = get Runtime::API::Request.new(@api_client, api_path, page: page_no, per_page: '100').url
if subgroups_response.code == HTTP_STATUS_OK
# Do not delete subgroups that are less than 4 days old (for debugging purposes)
subgroups.concat(parse_body(subgroups_response).select { |subgroup| Date.parse(subgroup[:created_at]) < @delete_before })
else
logger.error("Request for subgroups returned (#{subgroups_response.code}): `#{subgroups_response}` ")
end
page_no = subgroups_response.headers[:x_next_page].to_s
end
subgroups
end
def subgroup_request(subgroup, **options)
Runtime::API::Request.new(@api_client, "/groups/#{subgroup[:id]}", **options).url
end
def process_response_and_subgroup(response, subgroup, opts = {})
if response.code == 202
logger.info("Success\n")
opts[:save_successes_to] << subgroup if opts[:save_successes_to]
else
logger.error("Failed - #{response}\n")
@failed_deletion_attempts << { path: subgroup[:full_path], response: response }
end
end
def mark_for_deletion(subgroups)
subgroups_marked_for_deletion = []
logger.info("Marking #{subgroups.length} subgroups for deletion...\n")
subgroups.each do |subgroup|
path = subgroup[:full_path]
if subgroup[:marked_for_deletion_on].nil?
logger.info("Marking subgroup #{path} for deletion...")
response = delete(subgroup_request(subgroup))
process_response_and_subgroup(response, subgroup, save_successes_to: subgroups_marked_for_deletion)
else
logger.info("Subgroup #{path} already marked for deletion\n")
subgroups_marked_for_deletion << subgroup
end
end
subgroups_marked_for_deletion
end
def subgroup_exists?(subgroup)
response = get(subgroup_request(subgroup))
if response.code == 404
logger.info("Subgroup #{subgroup[:full_path]} is no longer available\n")
false
else
true
end
end
def delete_permanently(subgroups)
logger.info("Permanently deleting #{subgroups.length} subgroups...\n")
subgroups.each do |subgroup|
path = subgroup[:full_path]
next unless subgroup_exists?(subgroup)
logger.info("Permanently deleting subgroup #{path}...")
delete_subgroup_response = delete(subgroup_request(subgroup, permanently_remove: true, full_path: path))
process_response_and_subgroup(delete_subgroup_response, subgroup)
end
end
def print_failed_deletion_attempts
if @failed_deletion_attempts.empty?
logger.info('No failed deletion attempts to report!')
else
logger.info("There were #{@failed_deletion_attempts.length} failed deletion attempts:\n")
@failed_deletion_attempts.each do |attempt|
logger.info("Subgroup: #{attempt[:path]}")
logger.error("Response: #{attempt[:response]}\n")
end
end
end
end
end
end
|