allure-report-publisher 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,132 @@
1
+ require "gitlab"
2
+
3
+ module Publisher
4
+ module Providers
5
+ # Gitlab implementation
6
+ #
7
+ class Gitlab < Provider
8
+ # Get ci run ID without creating instance of ci provider
9
+ #
10
+ # @return [String]
11
+ def self.run_id
12
+ @run_id ||= ENV["CI_PIPELINE_ID"]
13
+ end
14
+
15
+ # Pull request run
16
+ #
17
+ # @return [Boolean]
18
+ def pr?
19
+ ENV["CI_PIPELINE_SOURCE"] == "merge_request_event"
20
+ end
21
+
22
+ # Get executor info
23
+ #
24
+ # @return [Hash]
25
+ def executor_info
26
+ {
27
+ name: "Gitlab",
28
+ type: "gitlab",
29
+ reportName: "AllureReport",
30
+ url: server_url,
31
+ reportUrl: report_url,
32
+ buildUrl: build_url,
33
+ buildOrder: run_id,
34
+ buildName: build_name
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ # Current pull request description
41
+ #
42
+ # @return [String]
43
+ def pr_description
44
+ @pr_description ||= client.merge_request(project, mr_iid).description
45
+ end
46
+
47
+ # Update pull request description
48
+ #
49
+ # @return [void]
50
+ def update_pr_description
51
+ client.update_merge_request(project, mr_iid, description: updated_pr_description)
52
+ end
53
+
54
+ # Add comment with report url
55
+ #
56
+ # @return [void]
57
+ def add_comment
58
+ return client.create_merge_request_comment(project, mr_iid, comment_body) unless comment
59
+
60
+ client.edit_merge_request_note(project, mr_iid, comment.id, comment_body)
61
+ end
62
+
63
+ # Existing comment with allure urls
64
+ #
65
+ # @return [Gitlab::ObjectifiedHash]
66
+ def comment
67
+ client.merge_request_comments(project, mr_iid).auto_paginate.detect do |comment|
68
+ comment.body.match?(DESCRIPTION_PATTERN)
69
+ end
70
+ end
71
+
72
+ # Get gitlab client
73
+ #
74
+ # @return [Gitlab::Client]
75
+ def client
76
+ @client ||= begin
77
+ raise("Missing GITLAB_AUTH_TOKEN environment variable!") unless ENV["GITLAB_AUTH_TOKEN"]
78
+
79
+ ::Gitlab::Client.new(
80
+ endpoint: "#{server_url}/api/v4",
81
+ private_token: ENV["GITLAB_AUTH_TOKEN"]
82
+ )
83
+ end
84
+ end
85
+
86
+ # Merge request iid
87
+ #
88
+ # @return [Integer]
89
+ def mr_iid
90
+ @mr_iid ||= ENV["CI_MERGE_REQUEST_IID"]
91
+ end
92
+
93
+ # Server url
94
+ #
95
+ # @return [String]
96
+ def server_url
97
+ @server_url ||= ENV["CI_SERVER_URL"]
98
+ end
99
+
100
+ # Build url
101
+ #
102
+ # @return [String]
103
+ def build_url
104
+ @build_url ||= ENV["CI_PIPELINE_URL"]
105
+ end
106
+
107
+ # Job name
108
+ #
109
+ # @return [String]
110
+ def build_name
111
+ @build_name ||= ENV[ALLURE_JOB_NAME] || ENV["CI_JOB_NAME"]
112
+ end
113
+
114
+ # Gitlab repository
115
+ #
116
+ # @return [String]
117
+ def project
118
+ @project ||= ENV["CI_PROJECT_PATH"]
119
+ end
120
+
121
+ # Commit sha url
122
+ #
123
+ # @return [String]
124
+ def sha_url
125
+ sha = ENV["CI_MERGE_REQUEST_SOURCE_BRANCH_SHA"] || ENV["CI_COMMIT_SHA"]
126
+ short_sha = sha[0..7]
127
+
128
+ "[#{short_sha}](#{server_url}/#{project}/-/merge_requests/#{mr_iid}/diffs?commit_id=#{sha})"
129
+ end
130
+ end
131
+ end
132
+ end
@@ -10,35 +10,32 @@ module Publisher
10
10
  class ReportGenerator
11
11
  include Helpers
12
12
 
13
- def initialize(results_glob, results_dir, report_dir)
13
+ def initialize(results_glob, results_path, report_path)
14
14
  @results_glob = results_glob
15
- @results_dir = results_dir
16
- @report_dir = report_dir
15
+ @results_path = results_path
16
+ @report_path = report_path
17
17
  end
18
18
 
19
19
  # Generate allure report
20
20
  #
21
21
  # @return [void]
22
22
  def generate
23
- log("Generating allure report")
24
- Helpers::Spinner.spin("generating report") do
25
- aggregate_results
26
- generate_report
27
- end
23
+ aggregate_results
24
+ generate_report
28
25
  end
29
26
 
30
27
  private
31
28
 
32
- attr_reader :results_glob, :results_dir, :report_dir
29
+ attr_reader :results_glob, :results_path, :report_path
33
30
 
34
31
  # Copy all results files to results directory
35
32
  #
36
33
  # @return [void]
37
34
  def aggregate_results
38
35
  results = Dir.glob(results_glob)
39
- error("Missing allure results") if results.empty?
36
+ raise(NoAllureResultsError, "Missing allure results") if results.empty?
40
37
 
41
- FileUtils.cp(results, results_dir)
38
+ FileUtils.cp(results, results_path)
42
39
  end
43
40
 
44
41
  # Generate allure report
@@ -46,9 +43,9 @@ module Publisher
46
43
  # @return [void]
47
44
  def generate_report
48
45
  out, _err, status = Open3.capture3(
49
- "allure generate --clean --output #{report_dir} #{results_dir}"
46
+ "allure generate --clean --output #{report_path} #{results_path}"
50
47
  )
51
- error(out) unless status.success?
48
+ raise(AllureError, out) unless status.success?
52
49
  end
53
50
  end
54
51
  end
@@ -1,10 +1,13 @@
1
1
  module Publisher
2
2
  module Uploaders
3
+ class HistoryNotFoundError < StandardError; end
4
+
3
5
  # Uploader implementation
4
6
  #
5
7
  class Uploader
6
8
  include Helpers
7
9
 
10
+ EXECUTOR_JSON = "executor.json".freeze
8
11
  HISTORY = [
9
12
  "categories-trend.json",
10
13
  "duration-trend.json",
@@ -13,61 +16,130 @@ module Publisher
13
16
  "retry-trend.json"
14
17
  ].freeze
15
18
 
16
- def initialize(results_glob, bucket, prefix = nil)
19
+ def initialize(results_glob:, bucket:, update_pr: nil, prefix: nil, copy_latest: false)
17
20
  @results_glob = results_glob
18
- @bucket = bucket
21
+ @bucket_name = bucket
19
22
  @prefix = prefix
23
+ @update_pr = update_pr
24
+ @copy_latest = !!(Providers.provider && copy_latest) # copy latest for ci only
20
25
  end
21
26
 
22
- # :nocov:
23
-
24
27
  # Execute allure report generation and upload
25
28
  #
26
29
  # @return [void]
27
30
  def execute
28
- raise(StandardError, "Not Implemented!")
31
+ generate_report
32
+ upload
33
+ add_url_to_pr
34
+ end
35
+
36
+ # Generate allure report
37
+ #
38
+ # @return [void]
39
+ def generate_report
40
+ add_history
41
+ add_executor_info
42
+
43
+ ReportGenerator.new(results_glob, results_path, report_path).generate
44
+ end
45
+
46
+ # Upload report to storage provider
47
+ #
48
+ # @return [void]
49
+ def upload
50
+ run_uploads
51
+ end
52
+
53
+ # Add allure report url to pull request description
54
+ #
55
+ # @return [void]
56
+ def add_url_to_pr
57
+ return unless update_pr && ci_provider
58
+
59
+ ci_provider.add_report_url
60
+ end
61
+
62
+ # Uploaded report urls
63
+ #
64
+ # @return [Hash<String, String>] uploaded report urls
65
+ def report_urls
66
+ urls = { "Report url" => report_url }
67
+ urls["Latest report url"] = latest_report_url if copy_latest
68
+
69
+ urls
70
+ end
71
+
72
+ # Executed in PR pipeline
73
+ #
74
+ # @return [Boolean]
75
+ def pr?
76
+ ci_provider&.pr?
29
77
  end
30
- # :nocov:
31
78
 
32
79
  private
33
80
 
34
- attr_reader :results_glob, :bucket, :prefix
81
+ attr_reader :results_glob, :bucket_name, :prefix, :update_pr, :copy_latest
35
82
 
36
83
  # :nocov:
37
84
 
85
+ # Cloud provider client
86
+ #
87
+ # @return [Object]
88
+ def client
89
+ raise("Not Implemented!")
90
+ end
91
+
38
92
  # Report url
39
93
  #
40
94
  # @return [String]
41
95
  def report_url
42
- raise(StandardError, "Not Implemented!")
96
+ raise("Not Implemented!")
43
97
  end
44
- # :nocov:
45
98
 
46
- # Get run id
99
+ # Latest report url
47
100
  #
48
101
  # @return [String]
49
- def run_id
50
- @run_id ||= CI.provider&.run_id
102
+ def latest_report_url
103
+ raise("Not Implemented!")
51
104
  end
52
105
 
53
- # Get CI provider
106
+ # Download allure history
54
107
  #
55
- # @return [Publisher::CI::Base]
56
- def ci_provider
57
- return @ci_provider if defined?(@ci_provider)
108
+ # @return [void]
109
+ def download_history
110
+ raise("Not implemented!")
111
+ end
112
+
113
+ # Upload history to s3
114
+ #
115
+ # @return [void]
116
+ def upload_history
117
+ raise("Not implemented!")
118
+ end
58
119
 
59
- @ci_provider = CI.provider&.new(results_dir, report_url)
120
+ # Upload report to s3
121
+ #
122
+ # @return [void]
123
+ def upload_report
124
+ raise("Not implemented!")
125
+ end
126
+
127
+ # Upload copy of latest run
128
+ #
129
+ # @return [void]
130
+ def upload_latest_copy
131
+ raise("Not implemented!")
60
132
  end
133
+ # :nocov:
61
134
 
62
135
  # Add allure history
63
136
  #
64
137
  # @return [void]
65
138
  def add_history
66
- log("Adding allure history")
67
- Helpers::Spinner.spin("adding history", exit_on_error: false) do
68
- create_history_dir
69
- yield
70
- end
139
+ create_history_dir
140
+ download_history
141
+ rescue HistoryNotFoundError
142
+ nil
71
143
  end
72
144
 
73
145
  # Add CI executor info
@@ -76,17 +148,41 @@ module Publisher
76
148
  def add_executor_info
77
149
  return unless ci_provider
78
150
 
79
- log("Adding executor info")
80
- Helpers::Spinner.spin("adding") do
81
- ci_provider.write_executor_info
151
+ File.open("#{results_path}/#{EXECUTOR_JSON}", "w") do |file|
152
+ file.write(ci_provider.executor_info.to_json)
82
153
  end
83
154
  end
84
155
 
156
+ # Run upload commands
157
+ #
158
+ # @return [void]
159
+ def run_uploads
160
+ upload_history unless !run_id || copy_latest
161
+ upload_report
162
+ upload_latest_copy if copy_latest
163
+ end
164
+
165
+ # Get run id
166
+ #
167
+ # @return [String]
168
+ def run_id
169
+ @run_id ||= Providers.provider&.run_id
170
+ end
171
+
172
+ # Get CI provider
173
+ #
174
+ # @return [Publisher::Providers::Base]
175
+ def ci_provider
176
+ return @ci_provider if defined?(@ci_provider)
177
+
178
+ @ci_provider = Providers.provider&.new(report_url: report_url, update_pr: update_pr)
179
+ end
180
+
85
181
  # Fetch allure report history
86
182
  #
87
183
  # @return [void]
88
184
  def create_history_dir
89
- FileUtils.mkdir_p(path(results_dir, "history"))
185
+ FileUtils.mkdir_p(path(results_path, "history"))
90
186
  end
91
187
 
92
188
  # Report path prefix
@@ -103,15 +199,15 @@ module Publisher
103
199
  # Aggregated results directory
104
200
  #
105
201
  # @return [String]
106
- def results_dir
107
- @results_dir ||= Dir.mktmpdir("allure-results")
202
+ def results_path
203
+ @results_path ||= Dir.mktmpdir("allure-results")
108
204
  end
109
205
 
110
206
  # Allure report directory
111
207
  #
112
208
  # @return [String]
113
- def report_dir
114
- @report_dir ||= Dir.mktmpdir("allure-report")
209
+ def report_path
210
+ @report_path ||= Dir.mktmpdir("allure-report")
115
211
  end
116
212
 
117
213
  # Report files
@@ -119,19 +215,9 @@ module Publisher
119
215
  # @return [Array<Pathname>]
120
216
  def report_files
121
217
  @report_files ||= Pathname
122
- .glob("#{report_dir}/**/*")
218
+ .glob("#{report_path}/**/*")
123
219
  .reject(&:directory?)
124
220
  end
125
-
126
- # Generate allure report
127
- #
128
- # @return [void]
129
- def generate_report
130
- add_history
131
- add_executor_info
132
-
133
- ReportGenerator.new(results_glob, results_dir, report_dir).generate
134
- end
135
221
  end
136
222
  end
137
223
  end
@@ -0,0 +1,104 @@
1
+ require "google/cloud/storage"
2
+
3
+ module Publisher
4
+ module Uploaders
5
+ # Google cloud storage uploader implementation
6
+ #
7
+ class GCS < Uploader
8
+ private
9
+
10
+ # GCS client
11
+ #
12
+ # @return [Google::Cloud::Storage::Project]
13
+ def client
14
+ @client ||= Google::Cloud::Storage.new
15
+ end
16
+
17
+ # GCS bucket
18
+ #
19
+ # @return [Google::Cloud::Storage::Bucket]
20
+ def bucket
21
+ @bucket ||= client.bucket(bucket_name, skip_lookup: true)
22
+ end
23
+
24
+ # Report url
25
+ #
26
+ # @return [String]
27
+ def report_url
28
+ @report_url ||= url(full_prefix)
29
+ end
30
+
31
+ # Latest report url
32
+ #
33
+ # @return [String]
34
+ def latest_report_url
35
+ @latest_report_url ||= url(prefix)
36
+ end
37
+
38
+ # Download allure history
39
+ #
40
+ # @return [void]
41
+ def download_history
42
+ HISTORY.each do |file_name|
43
+ file = bucket.file(key(prefix, "history", file_name))
44
+ raise(HistoryNotFoundError, "Allure history from previous runs not found!") unless file
45
+
46
+ file.download(path(results_path, "history", file_name))
47
+ end
48
+ end
49
+
50
+ # Upload allure history
51
+ #
52
+ # @return [void]
53
+ def upload_history
54
+ upload_to_gcs(report_files.select { |file| file.fnmatch?("*/history/*") }, prefix)
55
+ end
56
+
57
+ # Upload allure report
58
+ #
59
+ # @return [void]
60
+ def upload_report
61
+ upload_to_gcs(report_files, full_prefix)
62
+ end
63
+
64
+ # Upload copy of latest run
65
+ #
66
+ # @return [void]
67
+ def upload_latest_copy
68
+ upload_to_gcs(report_files, prefix)
69
+ end
70
+
71
+ # Upload files to s3
72
+ #
73
+ # @param [Array<Pathname>] files
74
+ # @param [String] key_prefix
75
+ # @return [Array<Hash>]
76
+ def upload_to_gcs(files, key_prefix)
77
+ args = files.map do |file|
78
+ {
79
+ file: file.to_s,
80
+ path: key(key_prefix, file.relative_path_from(report_path))
81
+ }
82
+ end
83
+
84
+ Parallel.each(args, in_threads: 8) { |obj| bucket.create_file(obj[:file], obj[:path]) }
85
+ end
86
+
87
+ # Fabricate key for s3 object
88
+ #
89
+ # @param [String] *args
90
+ # @return [String]
91
+ def key(*args)
92
+ args.compact.join("/")
93
+ end
94
+
95
+ # Report url
96
+ #
97
+ # @param [String] path_prefix
98
+ # @return [String]
99
+ def url(path_prefix)
100
+ ["https://storage.googleapis.com", bucket_name, path_prefix, "index.html"].compact.join("/")
101
+ end
102
+ end
103
+ end
104
+ end