gitlab-triage 1.28.0 → 1.29.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6db21779b13563c1dd43ffbe571507967c53dc22f4a75b61d84f2c2e5181a06
4
- data.tar.gz: 07141e346ff6ab3eca520ed37648b5431dd4ba54307d40358331323cc46b4ddc
3
+ metadata.gz: 6f594160679b71928b75f5bec0131c846d79b10cdc0cebb33388d1bc907b9769
4
+ data.tar.gz: 04a02e449720f03ab2820c3be0693c17486e366a7bcfe87a1c1f6e02ee6a02da
5
5
  SHA512:
6
- metadata.gz: 2acf8bcde530de9eb4365393431266f1acd824cdf48b95b8a557744503ed1af1b0a968256f8756ef337cbd7ad4b54aa330efbac2fa0632fadf628616adf3abdc
7
- data.tar.gz: 386c9a45639c9e8603409d573e6b16e42c3cbc8d281e7af5c5faac1fac882be40c38679d9c35dccf424bb5edd284e614287426ba8e0ac1344e6b42009458fd7a
6
+ metadata.gz: 44cc6360a513c7ad126ee8dbc4e5151fafbb8b4994e5e0ea84a2fdb5a32ec997271bd66ee3b065cbbbfb3e92548a38d599c025a6f32f378339522f836001cadf
7
+ data.tar.gz: 1177f4e8471c3fbe6606e5e0eeaee384335b327774a94574e9942d1928b7dec03c50b8c90df082091b7873f6f871c70a8b162732fa3740a222c861c1751f3f7a
data/README.md CHANGED
@@ -586,6 +586,7 @@ Example:
586
586
  ```yml
587
587
  conditions:
588
588
  health_status: Any
589
+ ```
589
590
 
590
591
  > **Note:** This query is not supported using GraphQL yet.
591
592
 
@@ -628,9 +629,9 @@ conditions:
628
629
  threshold: 15
629
630
  ```
630
631
 
631
- ##### Protected condition
632
+ ##### Protected condition
632
633
 
633
- ** This condition is only applicable for branches**
634
+ ** This condition is only applicable for branches**
634
635
 
635
636
  Accept a boolean.
636
637
  If not specified, default to `false` to filter out protected branches.
@@ -731,6 +732,7 @@ Available action types:
731
732
  - [`mention` action](#mention-action)
732
733
  - [`move` action](#move-action)
733
734
  - [`comment` action](#comment-action)
735
+ - [`redact_confidential_resources` option](#redact-confidential-resources-option)
734
736
  - [`comment_type` action option](#comment-type-action-option)
735
737
  - [`summarize` action](#summarize-action)
736
738
  - [`comment_on_summary` action](#comment-on-summary-action)
@@ -868,6 +870,22 @@ actions:
868
870
  {{author}} Are you still interested in finishing this merge request?
869
871
  ```
870
872
 
873
+ ###### Redact confidential resources option
874
+
875
+ Determines if the data of confidential resources is redacted.
876
+
877
+ If the option is set to `true` or not set, data from confidential will appear as `(confidential)`. \
878
+ When it is set to `false`, everything will be revealed and visible.
879
+
880
+ Example:
881
+
882
+ ```yml
883
+ actions:
884
+ redact_confidential_resources: false
885
+ comment: |
886
+ {{author}} Are you still interested in finishing this merge request?
887
+ ```
888
+
871
889
  ##### Comment type action option
872
890
 
873
891
  Determines the type of comment to be added to the resource.
@@ -1111,11 +1129,11 @@ resource_rules:
1111
1129
 
1112
1130
  **This action is only applicable for branches.**
1113
1131
 
1114
- Delete the resource.
1132
+ Delete the resource.
1115
1133
 
1116
1134
  Accept a boolean. Set to `true` to enable.
1117
1135
 
1118
- Example :
1136
+ Example :
1119
1137
  ```yaml
1120
1138
  resource_rules:
1121
1139
  branches:
@@ -1327,15 +1345,18 @@ Usage: gitlab-triage [options]
1327
1345
 
1328
1346
  -n, --dry-run Don't actually update anything, just print
1329
1347
  -f, --policies-file [string] A valid policies YML file
1348
+ --all-projects Process all projects the token has access to
1330
1349
  -s, --source [type] The source type between [ projects or groups ], default value: projects
1331
1350
  -i, --source-id [string] Source ID or path
1332
1351
  -p, --project-id [string] [Deprecated] A project ID or path, please use `--source-id`
1352
+ --resource-reference [string]
1353
+ Resource short-reference, e.g. #42, !33, or &99
1333
1354
  -t, --token [string] A valid API token
1334
1355
  -H, --host-url [string] A valid host url
1335
1356
  -r, --require [string] Require a file before performing
1336
1357
  -d, --debug Print debug information
1337
1358
  -h, --help Print help message
1338
- --all-projects Process all projects visible to `--token`
1359
+ -v, --version Print version
1339
1360
  --init Initialize the project with a policy file
1340
1361
  --init-ci Initialize the project with a .gitlab-ci.yml file
1341
1362
  ```
@@ -1372,6 +1393,14 @@ For example- after cloning this project, from the root `gitlab-triage` directory
1372
1393
  bundle exec bin/gitlab-triage --dry-run --token $GITLAB_API_TOKEN --source-id gitlab-org/triage
1373
1394
  ```
1374
1395
 
1396
+ Triaging against specific resource:
1397
+
1398
+ ```
1399
+ gitlab-triage --dry-run --token $API_TOKEN --source-id gitlab-org/triage --resource-reference '#42'
1400
+ gitlab-triage --dry-run --token $API_TOKEN --source-id gitlab-org/triage --resource-reference '!33'
1401
+ gitlab-triage --dry-run --token $API_TOKEN --source groups --source-id gitlab-org --resource-reference '&99'
1402
+ ```
1403
+
1375
1404
  #### Running on GitLab CI pipeline
1376
1405
 
1377
1406
  You can enforce policies using a scheduled pipeline:
@@ -45,7 +45,12 @@ module Gitlab
45
45
  def build_comment(resource)
46
46
  CommandBuilders::CommentCommandBuilder.new(
47
47
  [
48
- CommandBuilders::TextContentBuilder.new(policy.actions[:comment], resource: resource, network: network).build_command,
48
+ CommandBuilders::TextContentBuilder.new(
49
+ policy.actions[:comment],
50
+ resource: resource,
51
+ network: network,
52
+ redact_confidentials: policy.actions.fetch(:redact_confidential_resources, true)
53
+ ).build_command,
49
54
  CommandBuilders::LabelCommandBuilder.new(policy.actions[:labels], resource: resource, network: network).build_command,
50
55
  CommandBuilders::RemoveLabelCommandBuilder.new(policy.actions[:remove_labels], resource: resource, network: network).build_command,
51
56
  CommandBuilders::CcCommandBuilder.new(policy.actions[:mention]).build_command,
@@ -44,6 +44,7 @@ module Gitlab
44
44
  MILESTONE_TIMEBOX_VALUES = %w[none any upcoming started].freeze
45
45
  ITERATION_SELECTION_VALUES = %w[none any].freeze
46
46
  EpicsTriagingForProjectImpossibleError = Class.new(StandardError)
47
+ MultiPolicyInInjectionModeError = Class.new(StandardError)
47
48
 
48
49
  def initialize(policies:, options:, network_adapter_class: DEFAULT_NETWORK_ADAPTER, graphql_network_adapter_class: DEFAULT_GRAPHQL_ADAPTER)
49
50
  options.host_url = policies.delete(:host_url) { options.host_url }
@@ -56,21 +57,23 @@ module Gitlab
56
57
  @network_adapter_class = network_adapter_class
57
58
  @graphql_network_adapter_class = graphql_network_adapter_class
58
59
 
59
- assert_all!
60
- assert_project_id!
60
+ assert_options!
61
+
62
+ @options.source = @options.source.to_s
63
+
61
64
  require_ruby_files
62
65
  end
63
66
 
64
67
  def perform
65
68
  puts "Performing a dry run.\n\n" if options.dry_run
66
69
 
67
- puts Gitlab::Triage::UI.header("Triaging the `#{options.source_id}` #{options.source.to_s.singularize}", char: '=')
70
+ puts Gitlab::Triage::UI.header("Triaging the `#{options.source_id}` #{options.source.singularize}", char: '=')
68
71
  puts
69
72
 
70
73
  resource_rules.each do |resource_type, policy_definition|
71
- if resource_type == 'epics' && options.source != :groups
72
- raise(EpicsTriagingForProjectImpossibleError, "Epics can only be triaged at the group level. Please set the `--source groups` option.")
73
- end
74
+ next unless right_resource_type_for_resource_option?(resource_type)
75
+
76
+ assert_epic_rule!(resource_type)
74
77
 
75
78
  puts Gitlab::Triage::UI.header("Processing summaries & rules for #{resource_type}", char: '-')
76
79
  puts
@@ -94,22 +97,86 @@ module Gitlab
94
97
 
95
98
  private
96
99
 
97
- def assert_project_id!
100
+ def assert_options!
101
+ assert_all!
102
+ assert_source!
103
+ assert_source_id!
104
+ assert_resource_reference!
105
+ end
106
+
107
+ # rubocop:disable Style/IfUnlessModifier
108
+ def assert_all!
109
+ return unless options.all
110
+
111
+ if options.source
112
+ raise ArgumentError, '--all-projects option cannot be used in conjunction with --source option!'
113
+ end
114
+
115
+ if options.source_id
116
+ raise ArgumentError, '--all-projects option cannot be used in conjunction with --source-id option!'
117
+ end
118
+
119
+ if options.resource_reference # rubocop:disable Style/GuardClause
120
+ raise ArgumentError, '--all-projects option cannot be used in conjunction with --resource-reference option!'
121
+ end
122
+ end
123
+ # rubocop:enable Style/IfUnlessModifier
124
+
125
+ def assert_source!
126
+ return if options.source
127
+ return if options.all
128
+
129
+ raise ArgumentError, 'A source is needed (pass it with the `--source` option)!'
130
+ end
131
+
132
+ def assert_source_id!
98
133
  return if options.source_id
99
134
  return if options.all
100
135
 
101
- raise ArgumentError, 'A project_id is needed (pass it with the `--source-id` option)!'
136
+ raise ArgumentError, 'A project or group ID is needed (pass it with the `--source-id` option)!'
102
137
  end
103
138
 
104
- def assert_all!
105
- raise ArgumentError, '--all-projects option cannot be used in conjunction with --source and --source-id option!' if
106
- options.all && (options.source || options.source_id)
139
+ def assert_resource_reference!
140
+ return unless options.resource_reference
141
+
142
+ if options.source == 'groups' && !options.resource_reference.start_with?('&')
143
+ raise ArgumentError, "--resource-reference can only start with '&' when --source=groups is passed ('#{options.resource_reference}' passed)!"
144
+ end
145
+
146
+ if options.source == 'projects' && !options.resource_reference.start_with?('#', '!') # rubocop:disable Style/GuardClause
147
+ raise(
148
+ ArgumentError,
149
+ "--resource-reference can only start with '#' or '!' when --source=projects is passed " \
150
+ "('#{options.resource_reference}' passed)!"
151
+ )
152
+ end
107
153
  end
108
154
 
109
155
  def require_ruby_files
110
156
  options.require_files.each(&method(:require))
111
157
  end
112
158
 
159
+ def right_resource_type_for_resource_option?(resource_type)
160
+ return true unless options.resource_reference
161
+
162
+ resource_reference = options.resource_reference
163
+
164
+ case resource_type
165
+ when 'issues'
166
+ resource_reference.start_with?('#')
167
+ when 'merge_requests'
168
+ resource_reference.start_with?('!')
169
+ when 'epics'
170
+ resource_reference.start_with?('&')
171
+ end
172
+ end
173
+
174
+ def assert_epic_rule!(resource_type)
175
+ return if resource_type != 'epics' || options.source == 'groups'
176
+
177
+ raise EpicsTriagingForProjectImpossibleError, "Epics can only be triaged at the group level. Please set the `--source groups` option."
178
+ end
179
+
113
180
  def resource_rules
114
181
  @resource_rules ||= policies.delete(:resource_rules) { {} }
115
182
  end
@@ -279,20 +346,8 @@ module Gitlab
279
346
  ExpandCondition.perform(rule_conditions(rule_definition)) do |expanded_conditions|
280
347
  # retrieving the resources for every rule is inefficient
281
348
  # however, previous rules may affect those upcoming
282
- resources = []
283
-
284
- if rule_definition[:api] == 'graphql'
285
- graphql_query = build_graphql_query(resource_type, expanded_conditions, true)
286
- resources = graphql_network.query(graphql_query, source: source_full_path)
287
- else
288
- resources = network.query_api(build_get_url(resource_type, expanded_conditions))
289
- iids = resources.pluck('iid').map(&:to_s)
290
-
291
- graphql_query = build_graphql_query(resource_type, expanded_conditions)
292
- graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.any?
293
-
294
- decorate_resources_with_graphql_data(resources, graphql_resources)
295
- end
349
+ resources = options.resources ||
350
+ fetch_resources(resource_type, expanded_conditions, rule_definition)
296
351
 
297
352
  # In some filters/actions we want to know which resource type it is
298
353
  attach_resource_type(resources, resource_type)
@@ -310,6 +365,38 @@ module Gitlab
310
365
  end
311
366
  end
312
367
 
368
+ def fetch_resources(resource_type, expanded_conditions, rule_definition)
369
+ resources = []
370
+
371
+ if rule_definition[:api] == 'graphql'
372
+ graphql_query_options = { source: source_full_path }
373
+
374
+ if options.resource_reference
375
+ expanded_conditions[:iids] = options.resource_reference[1..]
376
+ graphql_query_options[:iids] = [expanded_conditions[:iids]]
377
+ end
378
+
379
+ graphql_query = build_graphql_query(resource_type, expanded_conditions, true)
380
+
381
+ resources = graphql_network.query(graphql_query, **graphql_query_options)
382
+ else
383
+ # FIXME: Epics listing endpoint doesn't support filtering by `iids`, so instead we
384
+ # get a single epic when `--resource-reference` is given for epics.
385
+ # Because of that, the query could return a single epic, so we make sure we get an array.
386
+ resources = Array(network.query_api(build_get_url(resource_type, expanded_conditions)))
387
+
388
+ iids = resources.pluck('iid').map(&:to_s)
389
+ expanded_conditions[:iids] = iids
390
+
391
+ graphql_query = build_graphql_query(resource_type, expanded_conditions)
392
+ graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.any?
393
+
394
+ decorate_resources_with_graphql_data(resources, graphql_resources)
395
+ end
396
+
397
+ resources
398
+ end
399
+
313
400
  def attach_resource_type(resources, resource_type)
314
401
  resources.each { |resource| resource[:type] = resource_type }
315
402
  end
@@ -394,6 +481,8 @@ module Gitlab
394
481
  end
395
482
  end
396
483
 
484
+ # rubocop:disable Metrics/AbcSize
485
+ # rubocop:disable Metrics/CyclomaticComplexity
397
486
  def build_get_url(resource_type, conditions)
398
487
  # Example issues query with state and labels
399
488
  # https://gitlab.com/api/v4/projects/test-triage%2Fissue-project/issues?state=open&labels=project%20label%20with%20spaces,group_label_no_spaces
@@ -402,6 +491,8 @@ module Gitlab
402
491
  }
403
492
 
404
493
  condition_builders = []
494
+ condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('iids', options.resource_reference[1..]) if options.resource_reference
495
+
405
496
  condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('labels', conditions[:labels], ',') if conditions[:labels]
406
497
 
407
498
  if conditions[:forbidden_labels]
@@ -434,15 +525,23 @@ module Gitlab
434
525
  params[condition_builder.param_name] = condition_builder.param_content
435
526
  end
436
527
 
437
- UrlBuilders::UrlBuilder.new(
528
+ url_builder_options = {
438
529
  network_options: options,
439
530
  all: options.all,
440
531
  source: options.source,
441
532
  source_id: options.source_id,
442
533
  resource_type: resource_type,
443
534
  params: params
444
- ).build
535
+ }
536
+
537
+ # FIXME: Epics listing endpoint doesn't support filtering by `iids`, so instead we
538
+ # get a single epic when `--resource-reference` is given for epics.
539
+ url_builder_options[:resource_id] = options.resource_reference[1..] if options.resource_reference && resource_type == 'epics'
540
+
541
+ UrlBuilders::UrlBuilder.new(url_builder_options).build
445
542
  end
543
+ # rubocop:enable Metrics/AbcSize
544
+ # rubocop:enable Metrics/CyclomaticComplexity
446
545
 
447
546
  def milestone_condition_builder(resource_type, milestone_condition)
448
547
  milestone_value = Array(milestone_condition)[0].to_s # back-compatibility
@@ -11,6 +11,10 @@ module Gitlab
11
11
  @resource_type = resource_type
12
12
  @conditions = conditions
13
13
  @graphql_only = graphql_only
14
+ @resource_declarations = [
15
+ '$source: ID!',
16
+ '$after: String'
17
+ ]
14
18
  end
15
19
 
16
20
  def resource_path
@@ -26,8 +30,7 @@ module Gitlab
26
30
  resource_type: resource_type.to_s.camelize(:lower),
27
31
  resource_fields: resource_fields.join(' '),
28
32
  resource_query: resource_query,
29
- iids_declaration: graphql_only ? nil : ', $iids: [String!]',
30
- iids_query: graphql_only ? nil : ', iids: $iids'
33
+ resource_declarations: resource_declarations.join(', ')
31
34
  )
32
35
  end
33
36
 
@@ -35,13 +38,13 @@ module Gitlab
35
38
 
36
39
  private
37
40
 
38
- attr_reader :source_type, :resource_type, :conditions, :graphql_only
41
+ attr_reader :source_type, :resource_type, :conditions, :graphql_only, :resource_declarations
39
42
 
40
43
  BASE_QUERY = <<~GRAPHQL.freeze
41
- query($source: ID!, $after: String%{iids_declaration}) {
44
+ query(%{resource_declarations}) {
42
45
  %{source_type}(fullPath: $source) {
43
46
  id
44
- %{resource_type}(after: $after%{iids_query}%{resource_query}) {
47
+ %{resource_type}(after: $after%{resource_query}) {
45
48
  pageInfo {
46
49
  hasNextPage
47
50
  endCursor
@@ -86,6 +89,11 @@ module Gitlab
86
89
  condition_queries << QueryParamBuilders::BaseParamBuilder.new('milestoneTitle', condition_params) if condition.to_s == 'milestone'
87
90
  condition_queries << QueryParamBuilders::BaseParamBuilder.new('state', condition_params, with_quotes: false) if condition.to_s == 'state'
88
91
 
92
+ if condition.to_s == 'iids'
93
+ @resource_declarations << '$iids: [String!]'
94
+ condition_queries << QueryParamBuilders::BaseParamBuilder.new('iids', '$iids', with_quotes: false)
95
+ end
96
+
89
97
  case resource_type
90
98
  when 'issues'
91
99
  condition_queries << issues_label_query(condition, condition_params)
@@ -43,6 +43,10 @@ module Gitlab
43
43
  options.source_id = value
44
44
  end
45
45
 
46
+ opts.on('--resource-reference [string]', String, 'Resource short-reference, e.g. #42, !33, or &99') do |value|
47
+ options.resource_reference = value
48
+ end
49
+
46
50
  opts.on('-t', '--token [string]', String, 'A valid API token') do |value|
47
51
  options.token = value
48
52
  end
@@ -3,9 +3,11 @@ module Gitlab
3
3
  Options = Struct.new(
4
4
  :dry_run,
5
5
  :policies_files,
6
+ :resources,
6
7
  :all,
7
8
  :source,
8
9
  :source_id,
10
+ :resource_reference,
9
11
  :token,
10
12
  :debug,
11
13
  :host_url,
@@ -14,6 +14,10 @@ module Gitlab
14
14
  def self.warn(text)
15
15
  "[WARNING] #{text}"
16
16
  end
17
+
18
+ def self.error(text)
19
+ "[ERROR] #{text}"
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Triage
5
- VERSION = '1.28.0'
5
+ VERSION = '1.29.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-triage
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.28.0
4
+ version: 1.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-29 00:00:00.000000000 Z
11
+ date: 2023-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport