gitlab-triage 1.28.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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