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
|
# frozen_string_literal: true
# rubocop:disable Style/SignalException
SEE_DOC = "See the [feature flag documentation](https://docs.gitlab.com/ee/development/feature_flags#feature-flag-definition-and-validation)."
FEATURE_FLAG_LABEL = "feature flag"
FEATURE_FLAG_EXISTS_LABEL = "#{FEATURE_FLAG_LABEL}::exists"
FEATURE_FLAG_SKIPPED_LABEL = "#{FEATURE_FLAG_LABEL}::skipped"
DEVOPS_LABELS_REQUIRING_FEATURE_FLAG_REVIEW = ["devops::verify"]
SUGGEST_MR_COMMENT = <<~SUGGEST_COMMENT
```suggestion
group: "%<group>s"
```
#{SEE_DOC}
SUGGEST_COMMENT
FEATURE_FLAG_ENFORCEMENT_WARNING = <<~WARNING_MESSAGE
There were no new or modified feature flag YAML files detected in this MR.
If the changes here are already controlled under an existing feature flag, please add
the ~"#{FEATURE_FLAG_EXISTS_LABEL}". Otherwise, if you think the changes here don't need
to be under a feature flag, please add the label ~"#{FEATURE_FLAG_SKIPPED_LABEL}", and
add a short comment about why we skipped the feature flag.
For guidance on when to use a feature flag, please see the [documentation](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags).
WARNING_MESSAGE
def check_feature_flag_yaml(feature_flag)
mr_group_label = helper.group_label
if feature_flag.group.nil?
message_for_feature_flag_missing_group!(feature_flag: feature_flag, mr_group_label: mr_group_label)
else
message_for_feature_flag_with_group!(feature_flag: feature_flag, mr_group_label: mr_group_label)
end
rescue Psych::Exception
# YAML could not be parsed, fail the build.
fail "#{helper.html_link(feature_flag.path)} isn't valid YAML! #{SEE_DOC}"
rescue StandardError => e
warn "There was a problem trying to check the Feature Flag file. Exception: #{e.class.name} - #{e.message}"
end
def message_for_feature_flag_missing_group!(feature_flag:, mr_group_label:)
if mr_group_label.nil?
warn "Consider setting `group` in #{helper.html_link(feature_flag.path)}. #{SEE_DOC}"
else
mr_line = feature_flag.raw.lines.find_index("group:\n")
if mr_line
markdown(format(SUGGEST_MR_COMMENT, group: mr_group_label), file: feature_flag.path, line: mr_line.succ)
else
warn %(Consider setting `group: "#{mr_group_label}"` in #{helper.html_link(feature_flag.path)}. #{SEE_DOC})
end
end
end
def message_for_global_rollout(feature_flag)
return unless feature_flag.default_enabled == true
message = <<~SUGGEST_COMMENT
You're about to [release the feature with the feature flag](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20Flag%20Roll%20Out.md#optional-release-the-feature-with-the-feature-flag).
This process can only be done **after** the [global rollout on production](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20Flag%20Roll%20Out.md#global-rollout-on-production).
Please make sure in [the rollout issue](#{feature_flag.rollout_issue_url}) that the preliminary steps have already been done. Otherwise, changing the YAML definition might not have the desired effect.
SUGGEST_COMMENT
mr_line = feature_flag.raw.lines.find_index { |l| l.include?('default_enabled:') }
markdown(message, file: feature_flag.path, line: mr_line.succ)
end
def message_for_feature_flag_with_group!(feature_flag:, mr_group_label:)
return if feature_flag.group_match_mr_label?(mr_group_label)
if mr_group_label.nil?
helper.labels_to_add << feature_flag.group
else
fail %(`group` is set to ~"#{feature_flag.group}" in #{helper.html_link(feature_flag.path)}, which does not match ~"#{mr_group_label}" set on the MR!)
end
end
def added_feature_flag_files
feature_flag.feature_flag_files(change_type: :added)
end
def modified_feature_flag_files
feature_flag.feature_flag_files(change_type: :modified)
end
def feature_flag_file_added?
added_feature_flag_files.any?
end
def feature_flag_file_modified?
modified_feature_flag_files.any?
end
def feature_flag_file_added_or_modified?
feature_flag_file_added? || feature_flag_file_modified?
end
def mr_has_backend_or_frontend_changes?
changes = helper.changes_by_category
changes.has_key?(:backend) || changes.has_key?(:frontend)
end
def mr_missing_feature_flag_status_label?
([FEATURE_FLAG_EXISTS_LABEL, FEATURE_FLAG_SKIPPED_LABEL] & helper.mr_labels).none?
end
def stage_requires_feature_flag_review?
DEVOPS_LABELS_REQUIRING_FEATURE_FLAG_REVIEW.include?(feature_flag.stage_label)
end
added_feature_flag_files.each do |feature_flag|
check_feature_flag_yaml(feature_flag)
end
modified_feature_flag_files.each do |feature_flag|
message_for_global_rollout(feature_flag)
end
if helper.security_mr? && feature_flag_file_added?
fail "Feature flags are discouraged from security merge requests. Read the [security documentation](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/feature_flags.md) for details."
end
if !helper.security_mr? && mr_has_backend_or_frontend_changes? && stage_requires_feature_flag_review?
if feature_flag_file_added_or_modified? && !helper.mr_has_labels?(FEATURE_FLAG_EXISTS_LABEL)
# Feature flag config file touched in this MR, so let's add the label to avoid the warning.
helper.labels_to_add << FEATURE_FLAG_EXISTS_LABEL
end
warn FEATURE_FLAG_ENFORCEMENT_WARNING if mr_missing_feature_flag_status_label?
end
|