127 lines
3.5 KiB
Ruby
127 lines
3.5 KiB
Ruby
|
#!/usr/bin/env ruby
|
||
|
|
||
|
# frozen_string_literal: true
|
||
|
|
||
|
require 'optparse'
|
||
|
require 'gitlab'
|
||
|
|
||
|
module Packages
|
||
|
class AutomatedCleanup
|
||
|
PACKAGES_PER_PAGE = 100
|
||
|
|
||
|
# $GITLAB_PROJECT_PACKAGES_CLEANUP_API_TOKEN => `Packages Cleanup` project token
|
||
|
def initialize(
|
||
|
project_path: ENV['CI_PROJECT_PATH'],
|
||
|
gitlab_token: ENV['GITLAB_PROJECT_PACKAGES_CLEANUP_API_TOKEN'],
|
||
|
api_endpoint: ENV['CI_API_V4_URL'],
|
||
|
options: {}
|
||
|
)
|
||
|
@project_path = project_path
|
||
|
@gitlab_token = gitlab_token
|
||
|
@api_endpoint = api_endpoint
|
||
|
@dry_run = options[:dry_run]
|
||
|
|
||
|
puts "Dry-run mode." if dry_run
|
||
|
end
|
||
|
|
||
|
def gitlab
|
||
|
@gitlab ||= begin
|
||
|
Gitlab.configure do |config|
|
||
|
config.endpoint = api_endpoint
|
||
|
config.private_token = gitlab_token
|
||
|
end
|
||
|
|
||
|
Gitlab
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def perform_gitlab_package_cleanup!(package_name:, days_for_delete:)
|
||
|
puts "Checking for '#{package_name}' packages created at least #{days_for_delete} days ago..."
|
||
|
|
||
|
gitlab.project_packages(project_path,
|
||
|
package_type: 'generic',
|
||
|
package_name: package_name,
|
||
|
per_page: PACKAGES_PER_PAGE).auto_paginate do |package|
|
||
|
next unless package.name == package_name # the search is fuzzy, so we better check the actual package name
|
||
|
|
||
|
if old_enough(package, days_for_delete) && not_recently_downloaded(package, days_for_delete)
|
||
|
delete_package(package)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
attr_reader :project_path, :gitlab_token, :api_endpoint, :dry_run
|
||
|
|
||
|
def delete_package(package)
|
||
|
print_package_state(package)
|
||
|
gitlab.delete_project_package(project_path, package.id) unless dry_run
|
||
|
rescue Gitlab::Error::Forbidden
|
||
|
puts "Package #{package_full_name(package)} is forbidden: skipping it"
|
||
|
end
|
||
|
|
||
|
def time_ago(days:)
|
||
|
Time.now - days * 24 * 3600
|
||
|
end
|
||
|
|
||
|
def old_enough(package, days_for_delete)
|
||
|
Time.parse(package.created_at) < time_ago(days: days_for_delete)
|
||
|
end
|
||
|
|
||
|
def not_recently_downloaded(package, days_for_delete)
|
||
|
package.last_downloaded_at.nil? ||
|
||
|
Time.parse(package.last_downloaded_at) < time_ago(days: days_for_delete)
|
||
|
end
|
||
|
|
||
|
def print_package_state(package)
|
||
|
download_text =
|
||
|
if package.last_downloaded_at
|
||
|
"last downloaded on #{package.last_downloaded_at}"
|
||
|
else
|
||
|
"never downloaded"
|
||
|
end
|
||
|
|
||
|
puts "\nPackage #{package_full_name(package)} (created on #{package.created_at}) was " \
|
||
|
"#{download_text}: deleting it.\n"
|
||
|
end
|
||
|
|
||
|
def package_full_name(package)
|
||
|
"'#{package.name}/#{package.version}'"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def timed(task)
|
||
|
start = Time.now
|
||
|
yield(self)
|
||
|
puts "#{task} finished in #{Time.now - start} seconds.\n"
|
||
|
end
|
||
|
|
||
|
if $PROGRAM_NAME == __FILE__
|
||
|
options = {
|
||
|
dry_run: false
|
||
|
}
|
||
|
|
||
|
OptionParser.new do |opts|
|
||
|
opts.on("-d", "--dry-run", "Whether to perform a dry-run or not.") do |value|
|
||
|
options[:dry_run] = true
|
||
|
end
|
||
|
|
||
|
opts.on("-h", "--help", "Prints this help") do
|
||
|
puts opts
|
||
|
exit
|
||
|
end
|
||
|
end.parse!
|
||
|
|
||
|
automated_cleanup = Packages::AutomatedCleanup.new(options: options)
|
||
|
|
||
|
timed('"gitlab-workhorse" packages cleanup') do
|
||
|
automated_cleanup.perform_gitlab_package_cleanup!(package_name: 'gitlab-workhorse', days_for_delete: 30)
|
||
|
end
|
||
|
|
||
|
timed('"assets" packages cleanup') do
|
||
|
automated_cleanup.perform_gitlab_package_cleanup!(package_name: 'assets', days_for_delete: 7)
|
||
|
end
|
||
|
end
|