allure-report-publisher 0.0.2 → 0.1.0

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