summaryrefslogtreecommitdiff
path: root/scripts/packages/automated_cleanup.rb
blob: 2b5a0011079c44b29cc14b03db7e1583d96c87a5 (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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/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