danger-additional-logging 0.0.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/LICENSE +22 -0
- data/README.md +93 -0
- data/bin/danger +5 -0
- data/lib/assets/DangerfileTemplate +13 -0
- data/lib/danger/ci_source/appcenter.rb +55 -0
- data/lib/danger/ci_source/appcircle.rb +83 -0
- data/lib/danger/ci_source/appveyor.rb +64 -0
- data/lib/danger/ci_source/azure_pipelines.rb +61 -0
- data/lib/danger/ci_source/bamboo.rb +41 -0
- data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
- data/lib/danger/ci_source/bitrise.rb +78 -0
- data/lib/danger/ci_source/buddybuild.rb +62 -0
- data/lib/danger/ci_source/buildkite.rb +51 -0
- data/lib/danger/ci_source/ci_source.rb +37 -0
- data/lib/danger/ci_source/circle.rb +94 -0
- data/lib/danger/ci_source/circle_api.rb +51 -0
- data/lib/danger/ci_source/cirrus.rb +31 -0
- data/lib/danger/ci_source/code_build.rb +71 -0
- data/lib/danger/ci_source/codefresh.rb +47 -0
- data/lib/danger/ci_source/codemagic.rb +58 -0
- data/lib/danger/ci_source/codeship.rb +44 -0
- data/lib/danger/ci_source/concourse.rb +60 -0
- data/lib/danger/ci_source/custom_ci_with_github.rb +49 -0
- data/lib/danger/ci_source/dotci.rb +50 -0
- data/lib/danger/ci_source/drone.rb +71 -0
- data/lib/danger/ci_source/github_actions.rb +44 -0
- data/lib/danger/ci_source/gitlab_ci.rb +89 -0
- data/lib/danger/ci_source/jenkins.rb +148 -0
- data/lib/danger/ci_source/local_git_repo.rb +117 -0
- data/lib/danger/ci_source/local_only_git_repo.rb +44 -0
- data/lib/danger/ci_source/screwdriver.rb +48 -0
- data/lib/danger/ci_source/semaphore.rb +37 -0
- data/lib/danger/ci_source/support/commits.rb +19 -0
- data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
- data/lib/danger/ci_source/support/find_repo_info_from_url.rb +43 -0
- data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
- data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
- data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
- data/lib/danger/ci_source/support/pull_request_finder.rb +190 -0
- data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
- data/lib/danger/ci_source/support/repo_info.rb +10 -0
- data/lib/danger/ci_source/surf.rb +37 -0
- data/lib/danger/ci_source/teamcity.rb +163 -0
- data/lib/danger/ci_source/travis.rb +51 -0
- data/lib/danger/ci_source/xcode_cloud.rb +38 -0
- data/lib/danger/ci_source/xcode_server.rb +48 -0
- data/lib/danger/clients/rubygems_client.rb +14 -0
- data/lib/danger/commands/dangerfile/gem.rb +43 -0
- data/lib/danger/commands/dangerfile/init.rb +30 -0
- data/lib/danger/commands/dry_run.rb +54 -0
- data/lib/danger/commands/init.rb +297 -0
- data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
- data/lib/danger/commands/local.rb +83 -0
- data/lib/danger/commands/local_helpers/http_cache.rb +38 -0
- data/lib/danger/commands/local_helpers/local_setup.rb +48 -0
- data/lib/danger/commands/local_helpers/pry_setup.rb +32 -0
- data/lib/danger/commands/plugins/plugin_json.rb +44 -0
- data/lib/danger/commands/plugins/plugin_lint.rb +52 -0
- data/lib/danger/commands/plugins/plugin_readme.rb +42 -0
- data/lib/danger/commands/pr.rb +93 -0
- data/lib/danger/commands/runner.rb +94 -0
- data/lib/danger/commands/staging.rb +53 -0
- data/lib/danger/commands/systems.rb +41 -0
- data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
- data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
- data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
- data/lib/danger/comment_generators/github.md.erb +55 -0
- data/lib/danger/comment_generators/github_inline.md.erb +26 -0
- data/lib/danger/comment_generators/gitlab.md.erb +40 -0
- data/lib/danger/comment_generators/gitlab_inline.md.erb +21 -0
- data/lib/danger/comment_generators/vsts.md.erb +20 -0
- data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
- data/lib/danger/core_ext/file_list.rb +18 -0
- data/lib/danger/core_ext/string.rb +20 -0
- data/lib/danger/danger_core/dangerfile.rb +348 -0
- data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
- data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
- data/lib/danger/danger_core/environment_manager.rb +126 -0
- data/lib/danger/danger_core/executor.rb +91 -0
- data/lib/danger/danger_core/message_aggregator.rb +50 -0
- data/lib/danger/danger_core/message_group.rb +68 -0
- data/lib/danger/danger_core/messages/base.rb +57 -0
- data/lib/danger/danger_core/messages/markdown.rb +41 -0
- data/lib/danger/danger_core/messages/violation.rb +53 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +142 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
- data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +274 -0
- data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +159 -0
- data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +264 -0
- data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +275 -0
- data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +43 -0
- data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +220 -0
- data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
- data/lib/danger/danger_core/standard_error.rb +142 -0
- data/lib/danger/helpers/array_subclass.rb +61 -0
- data/lib/danger/helpers/comment.rb +32 -0
- data/lib/danger/helpers/comments_helper.rb +179 -0
- data/lib/danger/helpers/comments_parsing_helper.rb +71 -0
- data/lib/danger/helpers/emoji_mapper.rb +41 -0
- data/lib/danger/helpers/find_max_num_violations.rb +31 -0
- data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
- data/lib/danger/plugin_support/gems_resolver.rb +77 -0
- data/lib/danger/plugin_support/plugin.rb +52 -0
- data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
- data/lib/danger/plugin_support/plugin_linter.rb +162 -0
- data/lib/danger/plugin_support/plugin_parser.rb +199 -0
- data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
- data/lib/danger/request_sources/bitbucket_cloud.rb +169 -0
- data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
- data/lib/danger/request_sources/bitbucket_server.rb +210 -0
- data/lib/danger/request_sources/bitbucket_server_api.rb +129 -0
- data/lib/danger/request_sources/code_insights_api.rb +142 -0
- data/lib/danger/request_sources/github/github.rb +535 -0
- data/lib/danger/request_sources/github/github_review.rb +127 -0
- data/lib/danger/request_sources/github/github_review_resolver.rb +17 -0
- data/lib/danger/request_sources/github/github_review_unsupported.rb +23 -0
- data/lib/danger/request_sources/gitlab.rb +557 -0
- data/lib/danger/request_sources/local_only.rb +50 -0
- data/lib/danger/request_sources/request_source.rb +97 -0
- data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
- data/lib/danger/request_sources/vsts.rb +278 -0
- data/lib/danger/request_sources/vsts_api.rb +172 -0
- data/lib/danger/scm_source/git_repo.rb +198 -0
- data/lib/danger/version.rb +4 -0
- data/lib/danger.rb +45 -0
- metadata +351 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require "danger/helpers/comments_helper"
|
|
2
|
+
require "danger/request_sources/bitbucket_cloud_api"
|
|
3
|
+
require "danger/danger_core/message_group"
|
|
4
|
+
|
|
5
|
+
module Danger
|
|
6
|
+
module RequestSources
|
|
7
|
+
class BitbucketCloud < RequestSource
|
|
8
|
+
include Danger::Helpers::CommentsHelper
|
|
9
|
+
attr_accessor :pr_json
|
|
10
|
+
|
|
11
|
+
def self.env_vars
|
|
12
|
+
[
|
|
13
|
+
"DANGER_BITBUCKETCLOUD_USERNAME",
|
|
14
|
+
"DANGER_BITBUCKETCLOUD_UUID",
|
|
15
|
+
"DANGER_BITBUCKETCLOUD_PASSWORD"
|
|
16
|
+
]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.optional_env_vars
|
|
20
|
+
["DANGER_BITBUCKETCLOUD_OAUTH_KEY", "DANGER_BITBUCKETCLOUD_OAUTH_SECRET"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(ci_source, environment)
|
|
24
|
+
self.ci_source = ci_source
|
|
25
|
+
|
|
26
|
+
@api = BitbucketCloudAPI.new(ci_source.repo_slug, ci_source.pull_request_id, nil, environment)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validates_as_ci?
|
|
30
|
+
# TODO: ???
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validates_as_api_source?
|
|
35
|
+
@api.credentials_given?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def scm
|
|
39
|
+
@scm ||= GitRepo.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def host
|
|
43
|
+
@host ||= @api.host
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def fetch_details
|
|
47
|
+
self.pr_json = @api.fetch_pr_json
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def setup_danger_branches
|
|
51
|
+
base_branch = self.pr_json[:destination][:branch][:name]
|
|
52
|
+
base_commit = self.pr_json[:destination][:commit][:hash]
|
|
53
|
+
head_branch = self.pr_json[:source][:branch][:name]
|
|
54
|
+
head_commit = self.pr_json[:source][:commit][:hash]
|
|
55
|
+
|
|
56
|
+
# Next, we want to ensure that we have a version of the current branch at a known location
|
|
57
|
+
scm.ensure_commitish_exists_on_branch! base_branch, base_commit
|
|
58
|
+
self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
|
|
59
|
+
|
|
60
|
+
# OK, so we want to ensure that we have a known head branch, this will always represent
|
|
61
|
+
# the head of the PR ( e.g. the most recent commit that will be merged. )
|
|
62
|
+
scm.ensure_commitish_exists_on_branch! head_branch, head_commit
|
|
63
|
+
self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def organisation
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
|
|
71
|
+
delete_old_comments(danger_id: danger_id) if !new_comment || remove_previous_comments
|
|
72
|
+
|
|
73
|
+
warnings = update_inline_comments_for_kind!(:warnings, warnings, danger_id: danger_id)
|
|
74
|
+
errors = update_inline_comments_for_kind!(:errors, errors, danger_id: danger_id)
|
|
75
|
+
messages = update_inline_comments_for_kind!(:messages, messages, danger_id: danger_id)
|
|
76
|
+
markdowns = update_inline_comments_for_kind!(:markdowns, markdowns, danger_id: danger_id)
|
|
77
|
+
|
|
78
|
+
has_comments = (warnings.count.positive? || errors.count.positive? || messages.count.positive? || markdowns.count.positive?)
|
|
79
|
+
if has_comments
|
|
80
|
+
comment = generate_description(warnings: warnings, errors: errors, template: "bitbucket_server")
|
|
81
|
+
comment += "\n\n"
|
|
82
|
+
comment += generate_comment(warnings: warnings,
|
|
83
|
+
errors: errors,
|
|
84
|
+
messages: messages,
|
|
85
|
+
markdowns: markdowns,
|
|
86
|
+
previous_violations: {},
|
|
87
|
+
danger_id: danger_id,
|
|
88
|
+
template: "bitbucket_server")
|
|
89
|
+
@api.post_comment(comment)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def update_pr_by_line!(message_groups:,
|
|
94
|
+
danger_id: "danger",
|
|
95
|
+
new_comment: false,
|
|
96
|
+
remove_previous_comments: false)
|
|
97
|
+
if !new_comment || remove_previous_comments
|
|
98
|
+
delete_old_comments(danger_id: danger_id)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
summary_body = generate_description(warnings: message_groups.fake_warnings_array,
|
|
102
|
+
errors: message_groups.fake_errors_array,
|
|
103
|
+
template: "bitbucket_server")
|
|
104
|
+
summary_body += "\n\n"
|
|
105
|
+
|
|
106
|
+
# this isn't the most elegant thing in the world, but we need the group
|
|
107
|
+
# with file: nil, line: nil so we can combine its info in with the
|
|
108
|
+
# summary_body
|
|
109
|
+
summary_group = message_groups.first
|
|
110
|
+
if summary_group && summary_group.file.nil? && summary_group.line.nil?
|
|
111
|
+
# remove summary_group from message_groups so it doesn't get a
|
|
112
|
+
# duplicate comment posted in the message_groups loop below
|
|
113
|
+
message_groups.shift
|
|
114
|
+
else
|
|
115
|
+
summary_group = MessageGroup.new(file: nil, line: nil)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
summary_body += generate_message_group_comment(
|
|
119
|
+
message_group: summary_group,
|
|
120
|
+
danger_id: danger_id,
|
|
121
|
+
template: "bitbucket_server_message_group"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@api.post_comment(summary_body)
|
|
125
|
+
|
|
126
|
+
message_groups.each do |message_group|
|
|
127
|
+
body = generate_message_group_comment(message_group: message_group,
|
|
128
|
+
danger_id: danger_id,
|
|
129
|
+
template: "bitbucket_server_message_group")
|
|
130
|
+
@api.post_comment(body,
|
|
131
|
+
file: message_group.file,
|
|
132
|
+
line: message_group.line)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def update_inline_comments_for_kind!(kind, messages, danger_id: "danger")
|
|
137
|
+
emoji = { warnings: "warning", errors: "no_entry_sign", messages: "book" }[kind]
|
|
138
|
+
|
|
139
|
+
messages.reject do |message|
|
|
140
|
+
next false unless message.file && message.line
|
|
141
|
+
|
|
142
|
+
body = ""
|
|
143
|
+
|
|
144
|
+
if kind == :markdown
|
|
145
|
+
body = generate_inline_markdown_body(message,
|
|
146
|
+
danger_id: danger_id,
|
|
147
|
+
template: "bitbucket_server")
|
|
148
|
+
else
|
|
149
|
+
body = generate_inline_comment_body(emoji, message,
|
|
150
|
+
danger_id: danger_id,
|
|
151
|
+
template: "bitbucket_server")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
@api.post_comment(body, file: message.file, line: message.line)
|
|
155
|
+
|
|
156
|
+
true
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def delete_old_comments(danger_id: "danger")
|
|
161
|
+
@api.fetch_comments.each do |c|
|
|
162
|
+
next if c[:user][:uuid] != @api.my_uuid
|
|
163
|
+
|
|
164
|
+
@api.delete_comment(c[:id]) if c[:content][:raw] =~ /generated_by_#{danger_id}/
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "danger/helpers/comments_helper"
|
|
4
|
+
|
|
5
|
+
module Danger
|
|
6
|
+
module RequestSources
|
|
7
|
+
class BitbucketCloudAPI
|
|
8
|
+
attr_accessor :host, :project, :slug, :access_token, :pull_request_id
|
|
9
|
+
attr_reader :my_uuid
|
|
10
|
+
|
|
11
|
+
def initialize(repo_slug, pull_request_id, branch_name, environment)
|
|
12
|
+
initialize_my_uuid(environment["DANGER_BITBUCKETCLOUD_UUID"])
|
|
13
|
+
@username = environment["DANGER_BITBUCKETCLOUD_USERNAME"]
|
|
14
|
+
@password = environment["DANGER_BITBUCKETCLOUD_PASSWORD"]
|
|
15
|
+
self.project, self.slug = repo_slug.split("/")
|
|
16
|
+
self.access_token = fetch_access_token(environment)
|
|
17
|
+
self.pull_request_id = pull_request_id || fetch_pr_from_branch(branch_name)
|
|
18
|
+
self.host = "https://bitbucket.org/"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize_my_uuid(uuid)
|
|
22
|
+
return if uuid.nil?
|
|
23
|
+
return @my_uuid = uuid if uuid.empty?
|
|
24
|
+
|
|
25
|
+
if uuid.start_with?("{") && uuid.end_with?("}")
|
|
26
|
+
@my_uuid = uuid
|
|
27
|
+
else
|
|
28
|
+
@my_uuid = "{#{uuid}}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def inspect
|
|
33
|
+
inspected = super
|
|
34
|
+
|
|
35
|
+
inspected.gsub!(@password, "********") if @password
|
|
36
|
+
inspected.gsub!(@access_token, "********") if @access_token
|
|
37
|
+
|
|
38
|
+
inspected
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def credentials_given?
|
|
42
|
+
@my_uuid && !@my_uuid.empty? &&
|
|
43
|
+
@username && !@username.empty? &&
|
|
44
|
+
@password && !@password.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pull_request(*)
|
|
48
|
+
fetch_pr_json
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def fetch_pr_json
|
|
52
|
+
uri = URI(pr_api_endpoint)
|
|
53
|
+
fetch_json(uri)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fetch_comments
|
|
57
|
+
values = []
|
|
58
|
+
# TODO: use a url parts encoder to encode the query
|
|
59
|
+
corrected_uuid = @my_uuid[1...-1] unless @my_uuid.nil? # Endpoint doesnt support curly brackets for this, so remove them for this
|
|
60
|
+
uri = "#{pr_api_endpoint}/comments?pagelen=100&q=deleted+%7E+false+AND+user.uuid+%7E+%22#{corrected_uuid}%22"
|
|
61
|
+
|
|
62
|
+
while uri
|
|
63
|
+
json = fetch_json(URI(uri))
|
|
64
|
+
values += json[:values]
|
|
65
|
+
uri = json[:next]
|
|
66
|
+
end
|
|
67
|
+
values
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def delete_comment(id)
|
|
71
|
+
uri = URI("#{pr_api_endpoint}/comments/#{id}")
|
|
72
|
+
delete(uri)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def post_comment(text, file: nil, line: nil)
|
|
76
|
+
uri = URI("#{pr_api_endpoint}/comments")
|
|
77
|
+
body = {
|
|
78
|
+
content: {
|
|
79
|
+
raw: text
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
body.merge!(inline: { path: file, to: line }) if file && line
|
|
83
|
+
|
|
84
|
+
post(uri, body.to_json)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def base_url(version)
|
|
90
|
+
"https://api.bitbucket.org/#{version}.0/repositories/#{project}/#{slug}/pullrequests"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def pr_api_endpoint
|
|
94
|
+
"#{base_url(2)}/#{pull_request_id}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def prs_api_url(branch_name)
|
|
98
|
+
encoded_branch_name = URI.encode_www_form_component(branch_name)
|
|
99
|
+
"#{base_url(2)}?q=source.branch.name=\"#{encoded_branch_name}\""
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fetch_pr_from_branch(branch_name)
|
|
103
|
+
uri = URI(prs_api_url(branch_name))
|
|
104
|
+
fetch_json(uri)[:values][0][:id]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def fetch_access_token(environment)
|
|
108
|
+
oauth_key = environment["DANGER_BITBUCKETCLOUD_OAUTH_KEY"]
|
|
109
|
+
oauth_secret = environment["DANGER_BITBUCKETCLOUD_OAUTH_SECRET"]
|
|
110
|
+
return nil if oauth_key.nil?
|
|
111
|
+
return nil if oauth_secret.nil?
|
|
112
|
+
|
|
113
|
+
uri = URI.parse("https://bitbucket.org/site/oauth2/access_token")
|
|
114
|
+
req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json" })
|
|
115
|
+
req.basic_auth oauth_key, oauth_secret
|
|
116
|
+
req.set_form_data({ "grant_type" => "client_credentials" })
|
|
117
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
118
|
+
http.request(req)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
JSON.parse(res.body, symbolize_names: true)[:access_token]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def fetch_json(uri)
|
|
125
|
+
raise credentials_not_available unless credentials_given?
|
|
126
|
+
|
|
127
|
+
req = Net::HTTP::Get.new(uri.request_uri, { "Content-Type" => "application/json" })
|
|
128
|
+
if access_token.nil?
|
|
129
|
+
req.basic_auth @username, @password
|
|
130
|
+
else
|
|
131
|
+
req["Authorization"] = "Bearer #{access_token}"
|
|
132
|
+
end
|
|
133
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
134
|
+
http.request(req)
|
|
135
|
+
end
|
|
136
|
+
raise error_fetching_json(uri.to_s, res.code) unless res.code == "200"
|
|
137
|
+
|
|
138
|
+
JSON.parse(res.body, symbolize_names: true)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def post(uri, body)
|
|
142
|
+
raise credentials_not_available unless credentials_given?
|
|
143
|
+
|
|
144
|
+
req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json" })
|
|
145
|
+
if access_token.nil?
|
|
146
|
+
req.basic_auth @username, @password
|
|
147
|
+
else
|
|
148
|
+
req["Authorization"] = "Bearer #{access_token}"
|
|
149
|
+
end
|
|
150
|
+
req.body = body
|
|
151
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
152
|
+
http.request(req)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def delete(uri)
|
|
157
|
+
raise credentials_not_available unless credentials_given?
|
|
158
|
+
|
|
159
|
+
req = Net::HTTP::Delete.new(uri.request_uri, { "Content-Type" => "application/json" })
|
|
160
|
+
if access_token.nil?
|
|
161
|
+
req.basic_auth @username, @password
|
|
162
|
+
else
|
|
163
|
+
req["Authorization"] = "Bearer #{access_token}"
|
|
164
|
+
end
|
|
165
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
166
|
+
http.request(req)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def credentials_not_available
|
|
171
|
+
"Credentials not available. Provide DANGER_BITBUCKETCLOUD_USERNAME, " \
|
|
172
|
+
"DANGER_BITBUCKETCLOUD_UUID, and DANGER_BITBUCKETCLOUD_PASSWORD " \
|
|
173
|
+
"as environment variables."
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def error_fetching_json(url, status_code)
|
|
177
|
+
"Error fetching json for: #{url}, status code: #{status_code}"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
require "danger/helpers/comments_helper"
|
|
2
|
+
require "danger/request_sources/bitbucket_server_api"
|
|
3
|
+
require "danger/request_sources/code_insights_api"
|
|
4
|
+
require_relative "request_source"
|
|
5
|
+
|
|
6
|
+
module Danger
|
|
7
|
+
module RequestSources
|
|
8
|
+
class BitbucketServer < RequestSource
|
|
9
|
+
include Danger::Helpers::CommentsHelper
|
|
10
|
+
attr_accessor :pr_json, :dismiss_out_of_range_messages
|
|
11
|
+
|
|
12
|
+
def self.env_vars
|
|
13
|
+
[
|
|
14
|
+
"DANGER_BITBUCKETSERVER_USERNAME",
|
|
15
|
+
"DANGER_BITBUCKETSERVER_PASSWORD",
|
|
16
|
+
"DANGER_BITBUCKETSERVER_HOST"
|
|
17
|
+
]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.optional_env_vars
|
|
21
|
+
[
|
|
22
|
+
"DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_KEY",
|
|
23
|
+
"DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_TITLE",
|
|
24
|
+
"DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_DESCRIPTION",
|
|
25
|
+
"DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_LOGO_URL",
|
|
26
|
+
"DANGER_BITBUCKETSERVER_VERIFY_SSL",
|
|
27
|
+
"DANGER_BITBUCKETSERVER_DISMISS_OUT_OF_RANGE_MESSAGES"
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(ci_source, environment)
|
|
32
|
+
self.ci_source = ci_source
|
|
33
|
+
self.dismiss_out_of_range_messages = environment["DANGER_BITBUCKETSERVER_DISMISS_OUT_OF_RANGE_MESSAGES"] == "true"
|
|
34
|
+
|
|
35
|
+
project, slug = ci_source.repo_slug.split("/")
|
|
36
|
+
@api = BitbucketServerAPI.new(project, slug, ci_source.pull_request_id, environment)
|
|
37
|
+
@code_insights = CodeInsightsAPI.new(project, slug, environment)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validates_as_ci?
|
|
41
|
+
# TODO: ???
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validates_as_api_source?
|
|
46
|
+
@api.credentials_given?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def scm
|
|
50
|
+
@scm ||= GitRepo.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def host
|
|
54
|
+
@host ||= @api.host
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def fetch_details
|
|
58
|
+
self.pr_json = @api.fetch_pr_json
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def pr_diff
|
|
62
|
+
@pr_diff ||= @api.fetch_pr_diff
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def setup_danger_branches
|
|
66
|
+
base_branch = self.pr_json[:toRef][:id].sub("refs/heads/", "")
|
|
67
|
+
base_commit = self.pr_json[:toRef][:latestCommit]
|
|
68
|
+
# Support for older versions of Bitbucket Server
|
|
69
|
+
base_commit = self.pr_json[:toRef][:latestChangeset] if self.pr_json[:fromRef].key? :latestChangeset
|
|
70
|
+
head_branch = self.pr_json[:fromRef][:id].sub("refs/heads/", "")
|
|
71
|
+
head_commit = self.pr_json[:fromRef][:latestCommit]
|
|
72
|
+
# Support for older versions of Bitbucket Server
|
|
73
|
+
head_commit = self.pr_json[:fromRef][:latestChangeset] if self.pr_json[:fromRef].key? :latestChangeset
|
|
74
|
+
|
|
75
|
+
# Next, we want to ensure that we have a version of the current branch at a known location
|
|
76
|
+
scm.ensure_commitish_exists_on_branch! base_branch, base_commit
|
|
77
|
+
self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
|
|
78
|
+
|
|
79
|
+
# OK, so we want to ensure that we have a known head branch, this will always represent
|
|
80
|
+
# the head of the PR ( e.g. the most recent commit that will be merged. )
|
|
81
|
+
scm.ensure_commitish_exists_on_branch! head_branch, head_commit
|
|
82
|
+
self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def organisation
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
|
|
90
|
+
delete_old_comments(danger_id: danger_id) if !new_comment || remove_previous_comments
|
|
91
|
+
|
|
92
|
+
# If configured, send a Code Insights API to provide the PR with a quality report
|
|
93
|
+
# which includes inline code violations found by Danger as Annotations.
|
|
94
|
+
# If no inline violations occurred, an empty, successful (green) report will be sent.
|
|
95
|
+
if @code_insights.ready?
|
|
96
|
+
inline_violations = inline_violations_group(warnings: warnings, errors: errors, messages: messages)
|
|
97
|
+
inline_warnings = inline_violations[:warnings] || []
|
|
98
|
+
inline_errors = inline_violations[:errors] || []
|
|
99
|
+
inline_messages = inline_violations[:messages] || []
|
|
100
|
+
|
|
101
|
+
head_commit = self.pr_json[:fromRef][:latestCommit]
|
|
102
|
+
@code_insights.send_report(head_commit,
|
|
103
|
+
inline_warnings,
|
|
104
|
+
inline_errors,
|
|
105
|
+
inline_messages)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# If we're sending inline comments separately via Code Insights,
|
|
109
|
+
# the main body comment should contain only generic, non-file specific messages.
|
|
110
|
+
if @code_insights.ready?
|
|
111
|
+
main_violations = main_violations_group(warnings: warnings, errors: errors, messages: messages)
|
|
112
|
+
warnings = main_violations[:warnings] || []
|
|
113
|
+
errors = main_violations[:errors] || []
|
|
114
|
+
messages = main_violations[:messages] || []
|
|
115
|
+
markdowns = main_violations[:markdowns] || []
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
has_comments = (warnings.count > 0 || errors.count > 0 || messages.count > 0 || markdowns.count > 0)
|
|
119
|
+
if has_comments
|
|
120
|
+
comment = generate_description(warnings: warnings,
|
|
121
|
+
errors: errors)
|
|
122
|
+
comment += "\n\n"
|
|
123
|
+
comment += generate_comment(warnings: warnings,
|
|
124
|
+
errors: errors,
|
|
125
|
+
messages: messages,
|
|
126
|
+
markdowns: markdowns,
|
|
127
|
+
previous_violations: {},
|
|
128
|
+
danger_id: danger_id,
|
|
129
|
+
template: "bitbucket_server")
|
|
130
|
+
@api.post_comment(comment)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def delete_old_comments(danger_id: "danger")
|
|
135
|
+
@api.fetch_last_comments.each do |c|
|
|
136
|
+
@api.delete_comment(c[:id], c[:version]) if c[:text] =~ /generated_by_#{danger_id}/
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def main_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
|
141
|
+
if dismiss_out_of_range_messages
|
|
142
|
+
{
|
|
143
|
+
warnings: warnings.reject(&:inline?),
|
|
144
|
+
errors: errors.reject(&:inline?),
|
|
145
|
+
messages: messages.reject(&:inline?),
|
|
146
|
+
markdowns: markdowns.reject(&:inline?)
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
in_diff = proc { |a| find_position_in_diff?(a.file, a.line) }
|
|
150
|
+
{
|
|
151
|
+
warnings: warnings.reject(&in_diff),
|
|
152
|
+
errors: errors.reject(&in_diff),
|
|
153
|
+
messages: messages.reject(&in_diff),
|
|
154
|
+
markdowns: markdowns.reject(&in_diff)
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
|
160
|
+
cmp = proc do |a, b|
|
|
161
|
+
next -1 unless a.file && a.line
|
|
162
|
+
next 1 unless b.file && b.line
|
|
163
|
+
|
|
164
|
+
next a.line <=> b.line if a.file == b.file
|
|
165
|
+
|
|
166
|
+
next a.file <=> b.file
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Sort to group inline comments by file
|
|
170
|
+
{
|
|
171
|
+
warnings: warnings.select(&:inline?).sort(&cmp),
|
|
172
|
+
errors: errors.select(&:inline?).sort(&cmp),
|
|
173
|
+
messages: messages.select(&:inline?).sort(&cmp),
|
|
174
|
+
markdowns: markdowns.select(&:inline?).sort(&cmp)
|
|
175
|
+
}
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def update_pr_build_status(status, build_job_link, description)
|
|
179
|
+
changeset = self.pr_json[:fromRef][:latestCommit]
|
|
180
|
+
# Support for older versions of Bitbucket Server
|
|
181
|
+
changeset = self.pr_json[:fromRef][:latestChangeset] if self.pr_json[:fromRef].key? :latestChangeset
|
|
182
|
+
puts "Changeset: " + changeset
|
|
183
|
+
puts self.pr_json.to_json
|
|
184
|
+
@api.update_pr_build_status(status, changeset, build_job_link, description)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def find_position_in_diff?(file, line)
|
|
188
|
+
return nil if file.nil? || line.nil?
|
|
189
|
+
return nil if file.empty?
|
|
190
|
+
|
|
191
|
+
added_lines(file).include?(line)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def file_diff(file)
|
|
195
|
+
self.pr_diff[:diffs].find { |diff| diff[:destination] && diff[:destination][:toString] == file } || { hunks: [] }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def added_lines(file)
|
|
199
|
+
@added_lines ||= {}
|
|
200
|
+
@added_lines[file] ||= file_diff(file)[:hunks].map do |hunk|
|
|
201
|
+
hunk[:segments].select { |segment| segment[:type] == "ADDED" }.map do |segment|
|
|
202
|
+
segment[:lines].map do |line|
|
|
203
|
+
line[:destination]
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end.flatten
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "danger/helpers/comments_helper"
|
|
5
|
+
|
|
6
|
+
module Danger
|
|
7
|
+
module RequestSources
|
|
8
|
+
class BitbucketServerAPI
|
|
9
|
+
attr_accessor :host, :verify_ssl, :pr_api_endpoint, :key, :project
|
|
10
|
+
|
|
11
|
+
def initialize(project, slug, pull_request_id, environment)
|
|
12
|
+
@username = environment["DANGER_BITBUCKETSERVER_USERNAME"]
|
|
13
|
+
@password = environment["DANGER_BITBUCKETSERVER_PASSWORD"]
|
|
14
|
+
self.host = environment["DANGER_BITBUCKETSERVER_HOST"]
|
|
15
|
+
self.verify_ssl = environment["DANGER_BITBUCKETSERVER_VERIFY_SSL"] != "false"
|
|
16
|
+
if self.host && !(self.host.include? "http://") && !(self.host.include? "https://")
|
|
17
|
+
self.host = "https://" + self.host
|
|
18
|
+
end
|
|
19
|
+
self.key = slug
|
|
20
|
+
self.project = project
|
|
21
|
+
self.pr_api_endpoint = "#{host}/rest/api/1.0/projects/#{project}/repos/#{slug}/pull-requests/#{pull_request_id}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inspect
|
|
25
|
+
inspected = super
|
|
26
|
+
|
|
27
|
+
inspected.gsub!(@password, "********") if @password
|
|
28
|
+
|
|
29
|
+
inspected
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def credentials_given?
|
|
33
|
+
@username && !@username.empty? && @password && !@password.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def pull_request(*)
|
|
37
|
+
fetch_pr_json
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fetch_pr_json
|
|
41
|
+
uri = URI(pr_api_endpoint)
|
|
42
|
+
fetch_json(uri)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def fetch_pr_diff
|
|
46
|
+
uri = URI("#{pr_api_endpoint}/diff?withComments=false")
|
|
47
|
+
fetch_json(uri)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def fetch_last_comments
|
|
51
|
+
uri = URI("#{pr_api_endpoint}/activities?limit=1000")
|
|
52
|
+
fetch_json(uri)[:values].select { |v| v[:action] == "COMMENTED" }.map { |v| v[:comment] }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def delete_comment(id, version)
|
|
56
|
+
uri = URI("#{pr_api_endpoint}/comments/#{id}?version=#{version}")
|
|
57
|
+
delete(uri)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def post_comment(text)
|
|
61
|
+
uri = URI("#{pr_api_endpoint}/comments")
|
|
62
|
+
body = { text: text }.to_json
|
|
63
|
+
post(uri, body)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def update_pr_build_status(status, changeset, build_job_link, description)
|
|
67
|
+
uri = URI("#{self.host}/rest/build-status/1.0/commits/#{changeset}")
|
|
68
|
+
body = build_status_body(status, build_job_link, description)
|
|
69
|
+
post(uri, body)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def use_ssl
|
|
75
|
+
return self.pr_api_endpoint.include? "https://"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def fetch_json(uri)
|
|
79
|
+
req = Net::HTTP::Get.new(uri.request_uri, { "Content-Type" => "application/json" })
|
|
80
|
+
req.basic_auth @username, @password
|
|
81
|
+
res = http(uri).start do |http|
|
|
82
|
+
http.request(req)
|
|
83
|
+
end
|
|
84
|
+
JSON.parse(res.body, symbolize_names: true)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def post(uri, body)
|
|
88
|
+
req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json" })
|
|
89
|
+
req.basic_auth @username, @password
|
|
90
|
+
req.body = body
|
|
91
|
+
|
|
92
|
+
res = http(uri).start do |http|
|
|
93
|
+
http.request(req)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# show error to the user when Bitbucket Server returned an error
|
|
97
|
+
case res
|
|
98
|
+
when Net::HTTPClientError, Net::HTTPServerError
|
|
99
|
+
# HTTP 4xx - 5xx
|
|
100
|
+
abort "\nError posting comment to Bitbucket Server: #{res.code} (#{res.message}) - #{res.body}\n\n"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def delete(uri)
|
|
105
|
+
req = Net::HTTP::Delete.new(uri.request_uri, { "Content-Type" => "application/json" })
|
|
106
|
+
req.basic_auth @username, @password
|
|
107
|
+
http(uri).start do |http|
|
|
108
|
+
http.request(req)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def http(uri)
|
|
113
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
|
114
|
+
http.use_ssl = use_ssl
|
|
115
|
+
http.verify_mode = verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
116
|
+
http
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_status_body(status, build_job_link, description)
|
|
120
|
+
body = {}
|
|
121
|
+
body["state"] = status
|
|
122
|
+
body["key"] = self.key
|
|
123
|
+
body["url"] = build_job_link
|
|
124
|
+
body["description"] = description if description
|
|
125
|
+
return body.to_json
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|