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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +93 -0
  4. data/bin/danger +5 -0
  5. data/lib/assets/DangerfileTemplate +13 -0
  6. data/lib/danger/ci_source/appcenter.rb +55 -0
  7. data/lib/danger/ci_source/appcircle.rb +83 -0
  8. data/lib/danger/ci_source/appveyor.rb +64 -0
  9. data/lib/danger/ci_source/azure_pipelines.rb +61 -0
  10. data/lib/danger/ci_source/bamboo.rb +41 -0
  11. data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
  12. data/lib/danger/ci_source/bitrise.rb +78 -0
  13. data/lib/danger/ci_source/buddybuild.rb +62 -0
  14. data/lib/danger/ci_source/buildkite.rb +51 -0
  15. data/lib/danger/ci_source/ci_source.rb +37 -0
  16. data/lib/danger/ci_source/circle.rb +94 -0
  17. data/lib/danger/ci_source/circle_api.rb +51 -0
  18. data/lib/danger/ci_source/cirrus.rb +31 -0
  19. data/lib/danger/ci_source/code_build.rb +71 -0
  20. data/lib/danger/ci_source/codefresh.rb +47 -0
  21. data/lib/danger/ci_source/codemagic.rb +58 -0
  22. data/lib/danger/ci_source/codeship.rb +44 -0
  23. data/lib/danger/ci_source/concourse.rb +60 -0
  24. data/lib/danger/ci_source/custom_ci_with_github.rb +49 -0
  25. data/lib/danger/ci_source/dotci.rb +50 -0
  26. data/lib/danger/ci_source/drone.rb +71 -0
  27. data/lib/danger/ci_source/github_actions.rb +44 -0
  28. data/lib/danger/ci_source/gitlab_ci.rb +89 -0
  29. data/lib/danger/ci_source/jenkins.rb +148 -0
  30. data/lib/danger/ci_source/local_git_repo.rb +117 -0
  31. data/lib/danger/ci_source/local_only_git_repo.rb +44 -0
  32. data/lib/danger/ci_source/screwdriver.rb +48 -0
  33. data/lib/danger/ci_source/semaphore.rb +37 -0
  34. data/lib/danger/ci_source/support/commits.rb +19 -0
  35. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  36. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +43 -0
  37. data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
  38. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  39. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  40. data/lib/danger/ci_source/support/pull_request_finder.rb +190 -0
  41. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  42. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  43. data/lib/danger/ci_source/surf.rb +37 -0
  44. data/lib/danger/ci_source/teamcity.rb +163 -0
  45. data/lib/danger/ci_source/travis.rb +51 -0
  46. data/lib/danger/ci_source/xcode_cloud.rb +38 -0
  47. data/lib/danger/ci_source/xcode_server.rb +48 -0
  48. data/lib/danger/clients/rubygems_client.rb +14 -0
  49. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  50. data/lib/danger/commands/dangerfile/init.rb +30 -0
  51. data/lib/danger/commands/dry_run.rb +54 -0
  52. data/lib/danger/commands/init.rb +297 -0
  53. data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
  54. data/lib/danger/commands/local.rb +83 -0
  55. data/lib/danger/commands/local_helpers/http_cache.rb +38 -0
  56. data/lib/danger/commands/local_helpers/local_setup.rb +48 -0
  57. data/lib/danger/commands/local_helpers/pry_setup.rb +32 -0
  58. data/lib/danger/commands/plugins/plugin_json.rb +44 -0
  59. data/lib/danger/commands/plugins/plugin_lint.rb +52 -0
  60. data/lib/danger/commands/plugins/plugin_readme.rb +42 -0
  61. data/lib/danger/commands/pr.rb +93 -0
  62. data/lib/danger/commands/runner.rb +94 -0
  63. data/lib/danger/commands/staging.rb +53 -0
  64. data/lib/danger/commands/systems.rb +41 -0
  65. data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
  66. data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
  67. data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
  68. data/lib/danger/comment_generators/github.md.erb +55 -0
  69. data/lib/danger/comment_generators/github_inline.md.erb +26 -0
  70. data/lib/danger/comment_generators/gitlab.md.erb +40 -0
  71. data/lib/danger/comment_generators/gitlab_inline.md.erb +21 -0
  72. data/lib/danger/comment_generators/vsts.md.erb +20 -0
  73. data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
  74. data/lib/danger/core_ext/file_list.rb +18 -0
  75. data/lib/danger/core_ext/string.rb +20 -0
  76. data/lib/danger/danger_core/dangerfile.rb +348 -0
  77. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  78. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  79. data/lib/danger/danger_core/environment_manager.rb +126 -0
  80. data/lib/danger/danger_core/executor.rb +91 -0
  81. data/lib/danger/danger_core/message_aggregator.rb +50 -0
  82. data/lib/danger/danger_core/message_group.rb +68 -0
  83. data/lib/danger/danger_core/messages/base.rb +57 -0
  84. data/lib/danger/danger_core/messages/markdown.rb +41 -0
  85. data/lib/danger/danger_core/messages/violation.rb +53 -0
  86. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +142 -0
  87. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
  88. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +274 -0
  89. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +159 -0
  90. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +264 -0
  91. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +275 -0
  92. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +43 -0
  93. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +220 -0
  94. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
  95. data/lib/danger/danger_core/standard_error.rb +142 -0
  96. data/lib/danger/helpers/array_subclass.rb +61 -0
  97. data/lib/danger/helpers/comment.rb +32 -0
  98. data/lib/danger/helpers/comments_helper.rb +179 -0
  99. data/lib/danger/helpers/comments_parsing_helper.rb +71 -0
  100. data/lib/danger/helpers/emoji_mapper.rb +41 -0
  101. data/lib/danger/helpers/find_max_num_violations.rb +31 -0
  102. data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
  103. data/lib/danger/plugin_support/gems_resolver.rb +77 -0
  104. data/lib/danger/plugin_support/plugin.rb +52 -0
  105. data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
  106. data/lib/danger/plugin_support/plugin_linter.rb +162 -0
  107. data/lib/danger/plugin_support/plugin_parser.rb +199 -0
  108. data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
  109. data/lib/danger/request_sources/bitbucket_cloud.rb +169 -0
  110. data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
  111. data/lib/danger/request_sources/bitbucket_server.rb +210 -0
  112. data/lib/danger/request_sources/bitbucket_server_api.rb +129 -0
  113. data/lib/danger/request_sources/code_insights_api.rb +142 -0
  114. data/lib/danger/request_sources/github/github.rb +535 -0
  115. data/lib/danger/request_sources/github/github_review.rb +127 -0
  116. data/lib/danger/request_sources/github/github_review_resolver.rb +17 -0
  117. data/lib/danger/request_sources/github/github_review_unsupported.rb +23 -0
  118. data/lib/danger/request_sources/gitlab.rb +557 -0
  119. data/lib/danger/request_sources/local_only.rb +50 -0
  120. data/lib/danger/request_sources/request_source.rb +97 -0
  121. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  122. data/lib/danger/request_sources/vsts.rb +278 -0
  123. data/lib/danger/request_sources/vsts_api.rb +172 -0
  124. data/lib/danger/scm_source/git_repo.rb +198 -0
  125. data/lib/danger/version.rb +4 -0
  126. data/lib/danger.rb +45 -0
  127. 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