payping-gitlab-triage 0.1.1
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 +7 -0
- data/.codeclimate.yml +19 -0
- data/.gitignore +15 -0
- data/.gitlab/CODEOWNERS +2 -0
- data/.gitlab/changelog_config.yml +13 -0
- data/.gitlab/issue_templates/Default.md +13 -0
- data/.gitlab/merge_request_templates/Default.md +11 -0
- data/.gitlab/merge_request_templates/Release.md +13 -0
- data/.gitlab-ci.yml +146 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +145 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.yardopts +4 -0
- data/CONTRIBUTING.md +31 -0
- data/Dangerfile +5 -0
- data/Gemfile +15 -0
- data/Guardfile +70 -0
- data/LICENSE.md +25 -0
- data/README.md +1480 -0
- data/Rakefile +6 -0
- data/bin/gitlab-triage +19 -0
- data/gitlab-triage.gemspec +41 -0
- data/lib/gitlab/triage/action/base.rb +14 -0
- data/lib/gitlab/triage/action/comment.rb +104 -0
- data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
- data/lib/gitlab/triage/action/delete.rb +56 -0
- data/lib/gitlab/triage/action/issue.rb +64 -0
- data/lib/gitlab/triage/action/summarize.rb +82 -0
- data/lib/gitlab/triage/action.rb +36 -0
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +27 -0
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +42 -0
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +28 -0
- data/lib/gitlab/triage/api_query_builders/single_query_param_builder.rb +13 -0
- data/lib/gitlab/triage/command_builders/base_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/cc_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/comment_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/label_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/move_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/remove_label_command_builder.rb +15 -0
- data/lib/gitlab/triage/command_builders/status_command_builder.rb +23 -0
- data/lib/gitlab/triage/command_builders/text_content_builder.rb +138 -0
- data/lib/gitlab/triage/engine.rb +635 -0
- data/lib/gitlab/triage/entity_builders/issue_builder.rb +54 -0
- data/lib/gitlab/triage/entity_builders/summary_builder.rb +82 -0
- data/lib/gitlab/triage/errors/network.rb +11 -0
- data/lib/gitlab/triage/errors.rb +1 -0
- data/lib/gitlab/triage/expand_condition/expansion.rb +203 -0
- data/lib/gitlab/triage/expand_condition/list.rb +25 -0
- data/lib/gitlab/triage/expand_condition/sequence.rb +25 -0
- data/lib/gitlab/triage/expand_condition.rb +23 -0
- data/lib/gitlab/triage/filters/assignee_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/author_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/base_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/branch_date_filter.rb +73 -0
- data/lib/gitlab/triage/filters/branch_protected_filter.rb +26 -0
- data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/issue_date_conditions_filter.rb +78 -0
- data/lib/gitlab/triage/filters/member_conditions_filter.rb +84 -0
- data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/name_conditions_filter.rb +26 -0
- data/lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb +30 -0
- data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +33 -0
- data/lib/gitlab/triage/filters/votes_conditions_filter.rb +54 -0
- data/lib/gitlab/triage/graphql_network.rb +81 -0
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +158 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +30 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/date_param_builder.rb +35 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/labels_param_builder.rb +18 -0
- data/lib/gitlab/triage/limiters/base_limiter.rb +35 -0
- data/lib/gitlab/triage/limiters/date_field_limiter.rb +45 -0
- data/lib/gitlab/triage/network.rb +39 -0
- data/lib/gitlab/triage/network_adapters/base_adapter.rb +17 -0
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +92 -0
- data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +116 -0
- data/lib/gitlab/triage/network_adapters/test_adapter.rb +39 -0
- data/lib/gitlab/triage/option_parser.rb +105 -0
- data/lib/gitlab/triage/options.rb +30 -0
- data/lib/gitlab/triage/param_builders/date_param_builder.rb +64 -0
- data/lib/gitlab/triage/policies/base_policy.rb +80 -0
- data/lib/gitlab/triage/policies/rule_policy.rb +36 -0
- data/lib/gitlab/triage/policies/summary_policy.rb +29 -0
- data/lib/gitlab/triage/policies_resources/rule_resources.rb +11 -0
- data/lib/gitlab/triage/policies_resources/summary_resources.rb +11 -0
- data/lib/gitlab/triage/resource/base.rb +102 -0
- data/lib/gitlab/triage/resource/branch.rb +13 -0
- data/lib/gitlab/triage/resource/context.rb +47 -0
- data/lib/gitlab/triage/resource/epic.rb +20 -0
- data/lib/gitlab/triage/resource/instance_version.rb +35 -0
- data/lib/gitlab/triage/resource/issue.rb +52 -0
- data/lib/gitlab/triage/resource/label.rb +56 -0
- data/lib/gitlab/triage/resource/label_event.rb +48 -0
- data/lib/gitlab/triage/resource/linked_issue.rb +15 -0
- data/lib/gitlab/triage/resource/merge_request.rb +23 -0
- data/lib/gitlab/triage/resource/milestone.rb +98 -0
- data/lib/gitlab/triage/resource/shared/issuable.rb +119 -0
- data/lib/gitlab/triage/rest_api_network.rb +125 -0
- data/lib/gitlab/triage/retryable.rb +33 -0
- data/lib/gitlab/triage/ui.rb +23 -0
- data/lib/gitlab/triage/url_builders/url_builder.rb +54 -0
- data/lib/gitlab/triage/utils.rb +13 -0
- data/lib/gitlab/triage/validators/limiter_validator.rb +21 -0
- data/lib/gitlab/triage/validators/params_validator.rb +43 -0
- data/lib/gitlab/triage/version.rb +7 -0
- data/lib/gitlab/triage.rb +6 -0
- data/support/.gitlab-ci.example.yml +22 -0
- data/support/.triage-policies.example.yml +51 -0
- metadata +280 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require_relative '../version'
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module NetworkAdapters
|
|
6
|
+
class BaseAdapter
|
|
7
|
+
USER_AGENT = "GitLab Triage #{Gitlab::Triage::VERSION}".freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :options
|
|
10
|
+
|
|
11
|
+
def initialize(options)
|
|
12
|
+
@options = options
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'graphql/client'
|
|
2
|
+
require 'graphql/client/http'
|
|
3
|
+
|
|
4
|
+
require_relative 'base_adapter'
|
|
5
|
+
require_relative '../ui'
|
|
6
|
+
require_relative '../errors'
|
|
7
|
+
|
|
8
|
+
module Gitlab
|
|
9
|
+
module Triage
|
|
10
|
+
module NetworkAdapters
|
|
11
|
+
class GraphqlAdapter < BaseAdapter
|
|
12
|
+
Client = GraphQL::Client
|
|
13
|
+
|
|
14
|
+
def query(graphql_query, resource_path: [], variables: {})
|
|
15
|
+
response = client.query(graphql_query, variables: variables, context: { token: options.token })
|
|
16
|
+
|
|
17
|
+
raise_on_error!(response)
|
|
18
|
+
|
|
19
|
+
parsed_response = parse_response(response, resource_path)
|
|
20
|
+
headers = response.extensions.fetch('headers', {})
|
|
21
|
+
|
|
22
|
+
graphql_response = {
|
|
23
|
+
ratelimit_remaining: headers['ratelimit-remaining'].to_i,
|
|
24
|
+
ratelimit_reset_at: Time.at(headers['ratelimit-reset'].to_i)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return graphql_response.merge(results: {}) if parsed_response.nil?
|
|
28
|
+
return graphql_response.merge(results: parsed_response.map(&:to_h)) if parsed_response.is_a?(Client::List)
|
|
29
|
+
return graphql_response.merge(results: parsed_response.to_h) unless parsed_response.nodes?
|
|
30
|
+
|
|
31
|
+
graphql_response.merge(
|
|
32
|
+
more_pages: parsed_response.page_info.has_next_page,
|
|
33
|
+
end_cursor: parsed_response.page_info.end_cursor,
|
|
34
|
+
results: parsed_response.nodes.map(&:to_h)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
delegate :parse, to: :client
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def parse_response(response, resource_path)
|
|
43
|
+
resource_path.reduce(response.data) { |data, resource| data&.send(resource) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def raise_on_error!(response)
|
|
47
|
+
return if response.errors.blank?
|
|
48
|
+
|
|
49
|
+
puts Gitlab::Triage::UI.debug response.inspect if options.debug
|
|
50
|
+
|
|
51
|
+
raise "There was an error: #{response.errors.messages.to_json}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def http_client
|
|
55
|
+
Client::HTTP.new("#{options.host_url}/api/graphql") do
|
|
56
|
+
def execute(document:, operation_name: nil, variables: {}, context: {}) # rubocop:disable Lint/NestedMethodDefinition
|
|
57
|
+
body = {}
|
|
58
|
+
body['query'] = document.to_query_string
|
|
59
|
+
body['variables'] = variables if variables.any?
|
|
60
|
+
body['operationName'] = operation_name if operation_name
|
|
61
|
+
|
|
62
|
+
response = HTTParty.post(
|
|
63
|
+
uri,
|
|
64
|
+
body: body.to_json,
|
|
65
|
+
headers: {
|
|
66
|
+
'User-Agent' => USER_AGENT,
|
|
67
|
+
'Content-type' => 'application/json',
|
|
68
|
+
'PRIVATE-TOKEN' => context[:token]
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
case response.code
|
|
73
|
+
when 200, 400
|
|
74
|
+
JSON.parse(response.body).merge('extensions' => { 'headers' => response.headers })
|
|
75
|
+
else
|
|
76
|
+
{ 'errors' => [{ 'message' => "#{response.code} #{response.message}" }] }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def schema
|
|
83
|
+
@schema ||= Client.load_schema(http_client)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def client
|
|
87
|
+
@client ||= Client.new(schema: schema, execute: http_client).tap { |client| client.allow_dynamic_queries = true }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
|
|
3
|
+
require_relative 'base_adapter'
|
|
4
|
+
require_relative '../ui'
|
|
5
|
+
require_relative '../errors'
|
|
6
|
+
|
|
7
|
+
module Gitlab
|
|
8
|
+
module Triage
|
|
9
|
+
module NetworkAdapters
|
|
10
|
+
class HttpartyAdapter < BaseAdapter
|
|
11
|
+
def get(token, url)
|
|
12
|
+
response = HTTParty.get(
|
|
13
|
+
url,
|
|
14
|
+
headers: {
|
|
15
|
+
'User-Agent' => USER_AGENT,
|
|
16
|
+
'Content-type' => 'application/json',
|
|
17
|
+
'PRIVATE-TOKEN' => token
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
raise_on_unauthorized_error!(response)
|
|
22
|
+
raise_on_internal_server_error!(response)
|
|
23
|
+
raise_on_too_many_requests!(response)
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
more_pages: (response.headers["x-next-page"].to_s != ""),
|
|
27
|
+
next_page_url: next_page_url(url, response),
|
|
28
|
+
results: response.parsed_response,
|
|
29
|
+
ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
|
|
30
|
+
ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def post(token, url, body)
|
|
35
|
+
response = HTTParty.post(
|
|
36
|
+
url,
|
|
37
|
+
body: body.to_json,
|
|
38
|
+
headers: {
|
|
39
|
+
'User-Agent' => "GitLab Triage #{Gitlab::Triage::VERSION}",
|
|
40
|
+
'Content-type' => 'application/json',
|
|
41
|
+
'PRIVATE-TOKEN' => token
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
raise_on_unauthorized_error!(response)
|
|
46
|
+
raise_on_internal_server_error!(response)
|
|
47
|
+
raise_on_too_many_requests!(response)
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
results: response.parsed_response,
|
|
51
|
+
ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
|
|
52
|
+
ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def delete(token, url)
|
|
57
|
+
response = HTTParty.delete(
|
|
58
|
+
url,
|
|
59
|
+
headers: {
|
|
60
|
+
'User-Agent' => USER_AGENT,
|
|
61
|
+
'PRIVATE-TOKEN' => token
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
raise_on_unauthorized_error!(response)
|
|
66
|
+
raise_on_internal_server_error!(response)
|
|
67
|
+
raise_on_too_many_requests!(response)
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
results: response.parsed_response,
|
|
71
|
+
ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
|
|
72
|
+
ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def raise_on_unauthorized_error!(response)
|
|
79
|
+
return unless response.response.is_a?(Net::HTTPUnauthorized)
|
|
80
|
+
|
|
81
|
+
puts Gitlab::Triage::UI.debug response.inspect if options.debug
|
|
82
|
+
|
|
83
|
+
raise 'The provided token is unauthorized!'
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def raise_on_internal_server_error!(response)
|
|
87
|
+
return unless response.response.is_a?(Net::HTTPInternalServerError)
|
|
88
|
+
|
|
89
|
+
puts Gitlab::Triage::UI.debug response.inspect if options.debug
|
|
90
|
+
|
|
91
|
+
raise Errors::Network::InternalServerError, 'Internal server error encountered!'
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def raise_on_too_many_requests!(response)
|
|
95
|
+
return unless response.response.is_a?(Net::HTTPTooManyRequests)
|
|
96
|
+
|
|
97
|
+
puts Gitlab::Triage::UI.debug response.inspect if options.debug
|
|
98
|
+
|
|
99
|
+
raise Errors::Network::TooManyRequests, 'Too many requests made!'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def next_page_url(url, response)
|
|
103
|
+
return unless response.headers['x-next-page'].present?
|
|
104
|
+
|
|
105
|
+
next_page = "&page=#{response.headers['x-next-page']}"
|
|
106
|
+
|
|
107
|
+
if url.include?('&page')
|
|
108
|
+
url.gsub(/&page=\d+/, next_page)
|
|
109
|
+
else
|
|
110
|
+
url + next_page
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
|
|
3
|
+
require_relative 'base_adapter'
|
|
4
|
+
|
|
5
|
+
module Gitlab
|
|
6
|
+
module Triage
|
|
7
|
+
module NetworkAdapters
|
|
8
|
+
class TestAdapter < BaseAdapter
|
|
9
|
+
def get(_token, url)
|
|
10
|
+
results =
|
|
11
|
+
case url
|
|
12
|
+
when %r{\Ahttps://gitlab.com/v4/issues?per_page=100}
|
|
13
|
+
[
|
|
14
|
+
{ id: 1, title: 'First issue' }
|
|
15
|
+
]
|
|
16
|
+
else
|
|
17
|
+
[]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
more_pages: false,
|
|
22
|
+
next_page_url: nil,
|
|
23
|
+
results: results,
|
|
24
|
+
ratelimit_remaining: 600,
|
|
25
|
+
ratelimit_reset_at: Time.now
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def post(token, url, body)
|
|
30
|
+
{
|
|
31
|
+
results: {},
|
|
32
|
+
ratelimit_remaining: 600,
|
|
33
|
+
ratelimit_reset_at: Time.now
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require_relative 'options'
|
|
6
|
+
|
|
7
|
+
module Gitlab
|
|
8
|
+
module Triage
|
|
9
|
+
class OptionParser
|
|
10
|
+
class << self
|
|
11
|
+
# rubocop:disable Metrics/AbcSize
|
|
12
|
+
def parse(argv)
|
|
13
|
+
options = Options.new
|
|
14
|
+
options.host_url = 'https://gitlab.com'
|
|
15
|
+
|
|
16
|
+
parser = ::OptionParser.new do |opts|
|
|
17
|
+
opts.banner = "Usage: gitlab-triage [options]\n\n"
|
|
18
|
+
|
|
19
|
+
opts.on('-n', '--dry-run', "Don't actually update anything, just print") do |value|
|
|
20
|
+
options.dry_run = value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
opts.on('-f', '--policies-file [string]', String, 'A valid policies YML file') do |value|
|
|
24
|
+
options.policies_files << value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
opts.on('--all-projects', 'Process all projects the token has access to') do |value|
|
|
28
|
+
options.all = value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
opts.on('-s', '--source [type]', [:projects, :groups], 'The source type between [ projects or groups ], default value: projects') do |value|
|
|
32
|
+
options.source = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.on('-i', '--source-id [string]', String, 'Source ID or path') do |value|
|
|
36
|
+
options.source_id = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on('-p', '--project-id [string]', String, '[Deprecated] A project ID or path, please use `--source-id`') do |value|
|
|
40
|
+
puts Gitlab::Triage::UI.warn("The option `--project-id` has been deprecated, please use `--source-id` instead")
|
|
41
|
+
puts
|
|
42
|
+
options.source = 'projects'
|
|
43
|
+
options.source_id = value
|
|
44
|
+
end
|
|
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
|
+
|
|
50
|
+
opts.on('-t', '--token [string]', String, 'A valid API token') do |value|
|
|
51
|
+
options.token = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
opts.on('-H', '--host-url [string]', String, 'A valid host url') do |value|
|
|
55
|
+
options.host_url = value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
opts.on('-r', '--require [string]', String, 'Require a file before performing') do |value|
|
|
59
|
+
options.require_files << value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
opts.on('-d', '--debug', 'Print debug information') do |value|
|
|
63
|
+
options.debug = value
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
opts.on('-h', '--help', 'Print help message') do
|
|
67
|
+
$stdout.puts opts
|
|
68
|
+
exit # rubocop:disable Rails/Exit
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
opts.on('-v', '--version', 'Print version') do
|
|
72
|
+
require_relative 'version'
|
|
73
|
+
$stdout.puts Gitlab::Triage::VERSION
|
|
74
|
+
exit # rubocop:disable Rails/Exit
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
opts.on('--init', 'Initialize the project with a policy file') do
|
|
78
|
+
example_path =
|
|
79
|
+
File.expand_path('../../../support/.triage-policies.example.yml', __dir__)
|
|
80
|
+
|
|
81
|
+
FileUtils.cp(example_path, '.triage-policies.yml')
|
|
82
|
+
exit # rubocop:disable Rails/Exit
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
opts.on('--init-ci', 'Initialize the project with a .gitlab-ci.yml file') do
|
|
86
|
+
example_path =
|
|
87
|
+
File.expand_path('../../../support/.gitlab-ci.example.yml', __dir__)
|
|
88
|
+
|
|
89
|
+
FileUtils.cp(example_path, '.gitlab-ci.yml')
|
|
90
|
+
exit # rubocop:disable Rails/Exit
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
parser.parse!(argv)
|
|
95
|
+
|
|
96
|
+
options.source = nil if options.all
|
|
97
|
+
options.token ||= ''
|
|
98
|
+
|
|
99
|
+
options
|
|
100
|
+
end
|
|
101
|
+
# rubocop:enable Metrics/AbcSize
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Gitlab
|
|
2
|
+
module Triage
|
|
3
|
+
Options = Struct.new(
|
|
4
|
+
:dry_run,
|
|
5
|
+
:policies_files,
|
|
6
|
+
:resources,
|
|
7
|
+
:all,
|
|
8
|
+
:source,
|
|
9
|
+
:source_id,
|
|
10
|
+
:resource_reference,
|
|
11
|
+
:token,
|
|
12
|
+
:debug,
|
|
13
|
+
:host_url,
|
|
14
|
+
:require_files,
|
|
15
|
+
:api_version
|
|
16
|
+
) do
|
|
17
|
+
def initialize(*args)
|
|
18
|
+
super
|
|
19
|
+
|
|
20
|
+
# Defaults
|
|
21
|
+
self.host_url ||= 'https://gitlab.com'
|
|
22
|
+
self.api_version ||= 'v4'
|
|
23
|
+
self.all ||= false
|
|
24
|
+
self.source ||= 'projects'
|
|
25
|
+
self.require_files ||= []
|
|
26
|
+
self.policies_files ||= Set.new
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require_relative '../validators/params_validator'
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module ParamBuilders
|
|
6
|
+
class DateParamBuilder
|
|
7
|
+
CONDITIONS = %w[older_than newer_than].freeze
|
|
8
|
+
TIME_BASED_INTERVALS = %w[minutes hours].freeze
|
|
9
|
+
DATE_BASED_INTERVALS = %w[days weeks months years].freeze
|
|
10
|
+
INTERVAL_TYPES = TIME_BASED_INTERVALS + DATE_BASED_INTERVALS
|
|
11
|
+
|
|
12
|
+
def initialize(allowed_attributes, condition_hash)
|
|
13
|
+
@allowed_attributes = allowed_attributes
|
|
14
|
+
@attribute = condition_hash[:attribute].to_s
|
|
15
|
+
@interval_condition = condition_hash[:condition].to_sym
|
|
16
|
+
@interval_type = condition_hash[:interval_type]
|
|
17
|
+
@interval = condition_hash[:interval]
|
|
18
|
+
|
|
19
|
+
validate_condition(condition_hash)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def param_content
|
|
23
|
+
if TIME_BASED_INTERVALS.include?(interval_type)
|
|
24
|
+
interval.public_send(interval_type).ago.to_datetime # rubocop:disable GitlabSecurity/PublicSend
|
|
25
|
+
else
|
|
26
|
+
interval.public_send(interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :allowed_attributes, :attribute, :interval_condition, :interval_type, :interval
|
|
33
|
+
|
|
34
|
+
def validate_condition(condition)
|
|
35
|
+
ParamsValidator.new(filter_parameters, condition).validate!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def filter_parameters
|
|
39
|
+
[
|
|
40
|
+
{
|
|
41
|
+
name: :attribute,
|
|
42
|
+
type: String,
|
|
43
|
+
values: allowed_attributes
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: :condition,
|
|
47
|
+
type: String,
|
|
48
|
+
values: CONDITIONS
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: :interval_type,
|
|
52
|
+
type: String,
|
|
53
|
+
values: INTERVAL_TYPES
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: :interval,
|
|
57
|
+
type: Numeric
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Triage
|
|
5
|
+
module Policies
|
|
6
|
+
class BasePolicy
|
|
7
|
+
InvalidPolicyError = Class.new(StandardError)
|
|
8
|
+
|
|
9
|
+
attr_reader :type, :policy_spec, :resources, :network
|
|
10
|
+
attr_accessor :summary
|
|
11
|
+
|
|
12
|
+
def initialize(type, policy_spec, resources, network)
|
|
13
|
+
@type = type
|
|
14
|
+
@policy_spec = policy_spec
|
|
15
|
+
@resources = resources
|
|
16
|
+
@network = network
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def validate!
|
|
20
|
+
raise InvalidPolicyError, 'Policies that comment_on_summary must include summarize action' if comment_on_summary? && !summarize?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def name
|
|
24
|
+
@name ||= (policy_spec[:name] || "#{type}-#{object_id}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def source
|
|
28
|
+
case type
|
|
29
|
+
when 'epics'
|
|
30
|
+
'groups'
|
|
31
|
+
else
|
|
32
|
+
'projects'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def source_id_sym
|
|
37
|
+
case type
|
|
38
|
+
when 'epics'
|
|
39
|
+
:group_id
|
|
40
|
+
else
|
|
41
|
+
:project_id
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def actions
|
|
46
|
+
@actions ||= policy_spec.fetch(:actions) { {} }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def summarize?
|
|
50
|
+
actions.key?(:summarize)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def comment_on_summary?
|
|
54
|
+
actions.key?(:comment_on_summary)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def comment?
|
|
58
|
+
# The actual keys are strings
|
|
59
|
+
(actions.keys.map(&:to_sym) - [:summarize, :comment_on_summary, :delete, :issue]).any?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def issue?
|
|
63
|
+
actions.key?(:issue)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def delete?
|
|
67
|
+
actions.key?(:delete) && actions[:delete]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def build_issue
|
|
71
|
+
raise NotImplementedError
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_summary
|
|
75
|
+
raise NotImplementedError
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_policy'
|
|
4
|
+
require_relative '../entity_builders/issue_builder'
|
|
5
|
+
require_relative '../entity_builders/summary_builder'
|
|
6
|
+
|
|
7
|
+
module Gitlab
|
|
8
|
+
module Triage
|
|
9
|
+
module Policies
|
|
10
|
+
class RulePolicy < BasePolicy
|
|
11
|
+
# Build a summary from a single rule policy
|
|
12
|
+
def build_summary
|
|
13
|
+
action = actions.fetch(:summarize, {})
|
|
14
|
+
|
|
15
|
+
EntityBuilders::SummaryBuilder.new(
|
|
16
|
+
type: type,
|
|
17
|
+
policy_spec: policy_spec,
|
|
18
|
+
action: action,
|
|
19
|
+
resources: resources,
|
|
20
|
+
network: network)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def build_issue(resource)
|
|
24
|
+
action = actions.fetch(:issue, {})
|
|
25
|
+
|
|
26
|
+
EntityBuilders::IssueBuilder.new(
|
|
27
|
+
type: type,
|
|
28
|
+
policy_spec: policy_spec,
|
|
29
|
+
action: action,
|
|
30
|
+
resource: resource,
|
|
31
|
+
network: network)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_policy'
|
|
4
|
+
require_relative '../entity_builders/summary_builder'
|
|
5
|
+
|
|
6
|
+
module Gitlab
|
|
7
|
+
module Triage
|
|
8
|
+
module Policies
|
|
9
|
+
class SummaryPolicy < BasePolicy
|
|
10
|
+
# Build a summary from several rules policies
|
|
11
|
+
def build_summary
|
|
12
|
+
action = actions[:summarize]
|
|
13
|
+
issues = resources.map do |inner_policy_spec, inner_resources|
|
|
14
|
+
Policies::RulePolicy.new(
|
|
15
|
+
type, inner_policy_spec, inner_resources, network)
|
|
16
|
+
.build_summary
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
EntityBuilders::SummaryBuilder.new(
|
|
20
|
+
type: type,
|
|
21
|
+
action: action,
|
|
22
|
+
resources: issues.select(&:any_resources?),
|
|
23
|
+
network: network,
|
|
24
|
+
separator: "\n\n")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|