summaryrefslogtreecommitdiff
path: root/scripts/used-feature-flags
blob: 89ea99c69842c5c52c44b9e03799cc3f938ca9ca (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
127
128
129
130
131
132
133
134
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'set'
require 'fileutils'
require_relative '../lib/gitlab_edition'

class String
  def red
    "\e[31m#{self}\e[0m"
  end

  def yellow
    "\e[33m#{self}\e[0m"
  end

  def green
    "\e[32m#{self}\e[0m"
  end

  def bold
    "\e[1m#{self}\e[0m"
  end
end

flags_paths = [
  'config/feature_flags/**/*.yml'
]

# For EE additionally process `ee/` feature flags
if GitlabEdition.ee?
  flags_paths << 'ee/config/feature_flags/**/*.yml'

  # Geo feature flags are constructed dynamically and there's no explicit checks in the codebase so we mark all
  # the replicators' derived feature flags as used.
  # See https://gitlab.com/gitlab-org/gitlab/-/blob/54e802e8fe76b6f93656d75ef9b566bf57b60f41/ee/lib/gitlab/geo/replicator.rb#L183-185
  Dir.glob('ee/app/replicators/geo/*_replicator.rb').each_with_object(Set.new) do |path, memo|
    replicator_name = File.basename(path, '.rb')
    feature_flag_name = "geo_#{replicator_name.delete_suffix('_replicator')}_replication"

    FileUtils.touch(File.join('tmp', 'feature_flags', "#{feature_flag_name}.used"))
  end
end

# For JH additionally process `jh/` feature flags
if GitlabEdition.jh?
  flags_paths << 'jh/config/feature_flags/**/*.yml'

  Dir.glob('jh/app/replicators/geo/*_replicator.rb').each_with_object(Set.new) do |path, memo|
    replicator_name = File.basename(path, '.rb')
    feature_flag_name = "geo_#{replicator_name.delete_suffix('_replicator')}_replication"

    FileUtils.touch(File.join('tmp', 'feature_flags', "#{feature_flag_name}.used"))
  end
end

all_flags = {}
additional_flags = Set.new

# Iterate all defined feature flags
# to discover which were used
flags_paths.each do |flags_path|
  puts flags_path
  Dir.glob(flags_path).each do |path|
    feature_flag_name = File.basename(path, '.yml')

    # TODO: we need a better way of tracking use of Gitaly FF across Gitaly and GitLab
    if feature_flag_name.start_with?('gitaly_')
      puts "Skipping the #{feature_flag_name} feature flag since it starts with 'gitaly_'."
      next
    end

    # Dynamic feature flag names for redirect to latest CI templates
    # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63144/diffs#fa2193ace3f6a02f7ef9995ef9bc519eca92c4ee_57_84
    if feature_flag_name.start_with?('redirect_to_latest_template_')
      puts "Skipping the #{feature_flag_name} feature flag since it starts with 'redirect_to_latest_template_'."
      next
    end

    all_flags[feature_flag_name] = File.exist?(File.join('tmp', 'feature_flags', feature_flag_name + '.used'))
  end
end

# Iterate all used feature flags
# to discover which flags are undefined
Dir.glob('tmp/feature_flags/*.used').each do |path|
  feature_flag_name = File.basename(path, '.used')

  additional_flags.add(feature_flag_name) unless all_flags[feature_flag_name]
end

used_flags = all_flags.select { |name, used| used }
unused_flags = all_flags.reject { |name, used| used }

puts "=========================================".green.bold
puts "Feature Flags usage summary:".green.bold
puts

puts "- #{all_flags.count + additional_flags.count} was found"
puts "- #{unused_flags.count} appear(s) to be UNUSED".yellow
puts "- #{additional_flags.count} appear(s) to be unknown".yellow
puts "- #{used_flags.count} appear(s) to be used".green
puts

if additional_flags.count > 0
  puts "==================================================".green.bold
  puts "There are feature flags that appears to be unknown".yellow
  puts
  puts "They appear to be used by CI, but we do lack their YAML definition".yellow
  puts "This is likely expected, so feel free to ignore that list:".yellow
  puts
  additional_flags.sort.each do |name|
    puts "- #{name}".yellow
  end
  puts
end

if unused_flags.count > 0
  puts "========================================".green.bold
  puts "These feature flags appears to be UNUSED".red.bold
  puts
  puts "If they are really no longer needed REMOVE their .yml definition".red
  puts "If they are needed you need to ENSURE that their usage is covered with specs to continue.".red
  puts
  unused_flags.keys.sort.each do |name|
    puts "- #{name}".yellow
  end
  puts
  puts "Feature flag usage check failed.".red.bold
  exit(1)
end

puts "Everything is fine here!".green
puts