allure-report-publisher 0.0.3 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,7 @@ module Publisher
4
4
  module Providers
5
5
  # Gitlab implementation
6
6
  #
7
- class Gitlab < Base
7
+ class Gitlab < Provider
8
8
  # Get ci run ID without creating instance of ci provider
9
9
  #
10
10
  # @return [String]
@@ -12,6 +12,13 @@ module Publisher
12
12
  @run_id ||= ENV["CI_PIPELINE_ID"]
13
13
  end
14
14
 
15
+ # Pull request run
16
+ #
17
+ # @return [Boolean]
18
+ def pr?
19
+ ENV["CI_PIPELINE_SOURCE"] == "merge_request_event"
20
+ end
21
+
15
22
  # Get executor info
16
23
  #
17
24
  # @return [Hash]
@@ -28,6 +35,8 @@ module Publisher
28
35
  }
29
36
  end
30
37
 
38
+ private
39
+
31
40
  # Current pull request description
32
41
  #
33
42
  # @return [String]
@@ -37,10 +46,27 @@ module Publisher
37
46
 
38
47
  # Update pull request description
39
48
  #
40
- # @param [String] desc
41
49
  # @return [void]
42
- def update_pr_description(desc)
43
- client.update_merge_request(project, mr_iid, description: desc)
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
44
70
  end
45
71
 
46
72
  # Get gitlab client
@@ -57,13 +83,6 @@ module Publisher
57
83
  end
58
84
  end
59
85
 
60
- # Pull request run
61
- #
62
- # @return [Boolean]
63
- def pr?
64
- ENV["CI_PIPELINE_SOURCE"] == "merge_request_event"
65
- end
66
-
67
86
  # Merge request iid
68
87
  #
69
88
  # @return [Integer]
@@ -89,15 +108,25 @@ module Publisher
89
108
  #
90
109
  # @return [String]
91
110
  def build_name
92
- @build_name ||= ENV["CI_JOB_NAME"]
111
+ @build_name ||= ENV[ALLURE_JOB_NAME] || ENV["CI_JOB_NAME"]
93
112
  end
94
113
 
95
- # Github repository
114
+ # Gitlab repository
96
115
  #
97
116
  # @return [String]
98
117
  def project
99
118
  @project ||= ENV["CI_PROJECT_PATH"]
100
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
101
130
  end
102
131
  end
103
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,36 +16,76 @@ 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
- def execute(update_pr: false)
28
- check_client_configured
29
-
30
+ def execute
30
31
  generate_report
31
- upload_history_and_report
32
- add_report_url if update_pr
33
- rescue StandardError => e
34
- error(e.message)
32
+ upload
33
+ add_url_to_pr
35
34
  end
36
35
 
37
- private
36
+ # Generate allure report
37
+ #
38
+ # @return [void]
39
+ def generate_report
40
+ add_history
41
+ add_executor_info
38
42
 
39
- attr_reader :results_glob, :bucket, :prefix
43
+ ReportGenerator.new(results_glob, results_path, report_path).generate
44
+ end
40
45
 
41
- # Validate if client is properly configured
42
- # and raise error if it is not
46
+ # Upload report to storage provider
43
47
  #
44
48
  # @return [void]
45
- def check_client_configured
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?
77
+ end
78
+
79
+ private
80
+
81
+ attr_reader :results_glob, :bucket_name, :prefix, :update_pr, :copy_latest
82
+
83
+ # :nocov:
84
+
85
+ # Cloud provider client
86
+ #
87
+ # @return [Object]
88
+ def client
46
89
  raise("Not Implemented!")
47
90
  end
48
91
 
@@ -53,58 +96,71 @@ module Publisher
53
96
  raise("Not Implemented!")
54
97
  end
55
98
 
56
- # Upload report to s3
99
+ # Latest report url
100
+ #
101
+ # @return [String]
102
+ def latest_report_url
103
+ raise("Not Implemented!")
104
+ end
105
+
106
+ # Download allure history
57
107
  #
58
108
  # @return [void]
59
- def upload_history_and_report
109
+ def download_history
60
110
  raise("Not implemented!")
61
111
  end
62
112
 
63
- # Add allure history
113
+ # Upload history to s3
64
114
  #
65
115
  # @return [void]
66
- def add_history
67
- log("Adding allure history")
68
- Helpers::Spinner.spin("adding history", exit_on_error: false) do
69
- create_history_dir
70
- yield
71
- end
116
+ def upload_history
117
+ raise("Not implemented!")
72
118
  end
73
119
 
74
- # Add CI executor info
120
+ # Upload report to s3
75
121
  #
76
122
  # @return [void]
77
- def add_executor_info
78
- return unless ci_provider
79
-
80
- log("Adding executor info")
81
- Helpers::Spinner.spin("adding executor") do
82
- ci_provider.write_executor_info
83
- end
123
+ def upload_report
124
+ raise("Not implemented!")
84
125
  end
85
126
 
86
- # Generate allure report
127
+ # Upload copy of latest run
87
128
  #
88
129
  # @return [void]
89
- def generate_report
90
- add_history
91
- add_executor_info
130
+ def upload_latest_copy
131
+ raise("Not implemented!")
132
+ end
133
+ # :nocov:
92
134
 
93
- ReportGenerator.new(results_glob, results_dir, report_dir).generate
135
+ # Add allure history
136
+ #
137
+ # @return [void]
138
+ def add_history
139
+ create_history_dir
140
+ download_history
141
+ rescue HistoryNotFoundError
142
+ nil
94
143
  end
95
144
 
96
- # Add allure report url to pull request description
145
+ # Add CI executor info
97
146
  #
98
147
  # @return [void]
99
- def add_report_url
148
+ def add_executor_info
100
149
  return unless ci_provider
101
150
 
102
- log("Adding allure report link to pr description")
103
- Helpers::Spinner.spin("adding link", exit_on_error: false) do
104
- ci_provider.add_report_url
151
+ File.open("#{results_path}/#{EXECUTOR_JSON}", "w") do |file|
152
+ file.write(ci_provider.executor_info.to_json)
105
153
  end
106
154
  end
107
- # :nocov:
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
108
164
 
109
165
  # Get run id
110
166
  #
@@ -119,14 +175,14 @@ module Publisher
119
175
  def ci_provider
120
176
  return @ci_provider if defined?(@ci_provider)
121
177
 
122
- @ci_provider = Providers.provider&.new(results_dir, report_url)
178
+ @ci_provider = Providers.provider&.new(report_url: report_url, update_pr: update_pr)
123
179
  end
124
180
 
125
181
  # Fetch allure report history
126
182
  #
127
183
  # @return [void]
128
184
  def create_history_dir
129
- FileUtils.mkdir_p(path(results_dir, "history"))
185
+ FileUtils.mkdir_p(path(results_path, "history"))
130
186
  end
131
187
 
132
188
  # Report path prefix
@@ -143,15 +199,15 @@ module Publisher
143
199
  # Aggregated results directory
144
200
  #
145
201
  # @return [String]
146
- def results_dir
147
- @results_dir ||= Dir.mktmpdir("allure-results")
202
+ def results_path
203
+ @results_path ||= Dir.mktmpdir("allure-results")
148
204
  end
149
205
 
150
206
  # Allure report directory
151
207
  #
152
208
  # @return [String]
153
- def report_dir
154
- @report_dir ||= Dir.mktmpdir("allure-report")
209
+ def report_path
210
+ @report_path ||= Dir.mktmpdir("allure-report")
155
211
  end
156
212
 
157
213
  # Report files
@@ -159,7 +215,7 @@ module Publisher
159
215
  # @return [Array<Pathname>]
160
216
  def report_files
161
217
  @report_files ||= Pathname
162
- .glob("#{report_dir}/**/*")
218
+ .glob("#{report_path}/**/*")
163
219
  .reject(&:directory?)
164
220
  end
165
221
  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