allure-report-publisher 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/lib/allure_report_publisher/commands/upload.rb +43 -8
- data/lib/allure_report_publisher/lib/helpers/gsutil.rb +120 -0
- data/lib/allure_report_publisher/lib/helpers/helpers.rb +78 -20
- data/lib/allure_report_publisher/lib/helpers/spinner.rb +32 -9
- data/lib/allure_report_publisher/lib/helpers/summary.rb +1 -1
- data/lib/allure_report_publisher/lib/providers/github.rb +8 -1
- data/lib/allure_report_publisher/lib/providers/gitlab.rb +8 -1
- data/lib/allure_report_publisher/lib/report_generator.rb +13 -6
- data/lib/allure_report_publisher/lib/uploaders/_uploader.rb +11 -3
- data/lib/allure_report_publisher/lib/uploaders/gcs.rb +37 -3
- data/lib/allure_report_publisher/lib/uploaders/s3.rb +13 -4
- data/lib/allure_report_publisher/version.rb +1 -1
- data/lib/allure_report_publisher.rb +1 -1
- metadata +24 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc21e0f3488c76bf88a227a59a470d1959f5aadb813fc86986f7a7a175793efc
|
4
|
+
data.tar.gz: 641b2e9a9e6587a3246bdcdbf4e34cc3b952f56c46773e495473db9749e68e80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ef72a1f8869b175110a7377418bc3a4b369bbb44329965b2b76184c905dc38293b845cf0d661dbe1ff6eb66c2b87804d6fdc74b06eefa58d30826b863c6f0f9
|
7
|
+
data.tar.gz: d15121ee77d77a467a7e9f6a28b447218711aacc3aa63586f2a0cac424e1abdd129ac2aa7aaba654ddce25f8cf4aec0049d39d4b6cf8be8348515cac6f1854de
|
data/README.md
CHANGED
@@ -53,6 +53,7 @@ Options:
|
|
53
53
|
--[no-]copy-latest # Keep copy of latest report at base prefix path, default: false
|
54
54
|
--[no-]color # Force color output
|
55
55
|
--[no-]ignore-missing-results # Ignore missing allure results, default: false
|
56
|
+
--[no-]debug # Print additional debug output, default: false
|
56
57
|
--help, -h # Print this help
|
57
58
|
|
58
59
|
Examples:
|
@@ -55,6 +55,10 @@ module Publisher
|
|
55
55
|
type: :boolean,
|
56
56
|
default: false,
|
57
57
|
desc: "Ignore missing allure results"
|
58
|
+
option :debug,
|
59
|
+
type: :boolean,
|
60
|
+
default: false,
|
61
|
+
desc: "Print additional debug output"
|
58
62
|
|
59
63
|
example [
|
60
64
|
"s3 --results-glob='path/to/allure-results' --bucket=my-bucket",
|
@@ -68,16 +72,13 @@ module Publisher
|
|
68
72
|
validate_args
|
69
73
|
validate_result_files
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
log("Uploading allure report to #{args[:type]}")
|
75
|
-
Spinner.spin("uploading") { uploader.upload }
|
76
|
-
uploader.report_urls.each { |k, v| log("#{k}: #{v}", :green) }
|
75
|
+
generate_report
|
76
|
+
upload_report
|
77
77
|
return unless args[:update_pr] && uploader.pr?
|
78
78
|
|
79
|
-
|
80
|
-
|
79
|
+
add_report_urls
|
80
|
+
rescue StandardError => e
|
81
|
+
handle_error(e)
|
81
82
|
end
|
82
83
|
|
83
84
|
private
|
@@ -133,6 +134,40 @@ module Publisher
|
|
133
134
|
log("Glob '#{results_glob}' did not match any files!", ignore ? :yellow : :red)
|
134
135
|
exit(ignore ? 0 : 1)
|
135
136
|
end
|
137
|
+
|
138
|
+
# Generate allure report
|
139
|
+
#
|
140
|
+
# @return [void]
|
141
|
+
def generate_report
|
142
|
+
log("Generating allure report")
|
143
|
+
Spinner.spin("generating", debug: args[:debug]) { uploader.generate_report }
|
144
|
+
end
|
145
|
+
|
146
|
+
# Upload report to cloud storage
|
147
|
+
#
|
148
|
+
# @return [void]
|
149
|
+
def upload_report
|
150
|
+
log("Uploading allure report to #{args[:type]}")
|
151
|
+
Spinner.spin("uploading", debug: args[:debug]) { uploader.upload }
|
152
|
+
uploader.report_urls.each { |k, v| log("#{k}: #{v}", :green) }
|
153
|
+
end
|
154
|
+
|
155
|
+
# Add report results to pr/mr
|
156
|
+
#
|
157
|
+
# @return [void]
|
158
|
+
def add_report_urls
|
159
|
+
log("Adding reports urls")
|
160
|
+
Spinner.spin("updating", exit_on_error: false, debug: args[:debug]) { uploader.add_result_summary }
|
161
|
+
end
|
162
|
+
|
163
|
+
# Handle error during upload command
|
164
|
+
#
|
165
|
+
# @param [StandardError] error
|
166
|
+
# @return [void]
|
167
|
+
def handle_error(error)
|
168
|
+
exit(1) if error.is_a?(Spinner::Failure)
|
169
|
+
error(error)
|
170
|
+
end
|
136
171
|
end
|
137
172
|
end
|
138
173
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
|
3
|
+
module Publisher
|
4
|
+
module Helpers
|
5
|
+
# Helper class for gsutil cli utility
|
6
|
+
#
|
7
|
+
class Gsutil
|
8
|
+
class UnsupportedConfig < StandardError; end
|
9
|
+
class Uninitialised < StandardError; end
|
10
|
+
|
11
|
+
include Helpers
|
12
|
+
|
13
|
+
def self.init
|
14
|
+
new.init!
|
15
|
+
end
|
16
|
+
|
17
|
+
private_class_method :new
|
18
|
+
|
19
|
+
# Initialize gsutil
|
20
|
+
#
|
21
|
+
# @return [Gsutil]
|
22
|
+
def init!
|
23
|
+
log_debug("Setting up gsutil")
|
24
|
+
@valid = execute_shell("which gsutil") && true
|
25
|
+
|
26
|
+
log_debug("Checking google credentials")
|
27
|
+
check_credentials
|
28
|
+
log_debug("Credentials valid, gsutil initialized")
|
29
|
+
self
|
30
|
+
rescue StandardError => e
|
31
|
+
case e
|
32
|
+
when UnsupportedConfig
|
33
|
+
log_debug("credentials not compatible with gsutil! Falling back to google sdk client for batch uploads")
|
34
|
+
when ShellCommandFailure
|
35
|
+
log_debug("gsutil command not found, falling back to gcs client")
|
36
|
+
else
|
37
|
+
log_debug("gsutil init failed: error: #{e}\nbacktrace: #{e.backtrace&.join("\n")}")
|
38
|
+
end
|
39
|
+
|
40
|
+
@valid = false
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check if gsutil is valid
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
47
|
+
def valid?
|
48
|
+
@valid
|
49
|
+
end
|
50
|
+
|
51
|
+
# Perform batch copy operation
|
52
|
+
#
|
53
|
+
# @param [String] source_dir
|
54
|
+
# @param [String] destination_dir
|
55
|
+
# @param [String] bucket
|
56
|
+
# @param [String] cache_control
|
57
|
+
# @return [void]
|
58
|
+
def batch_copy(source_dir:, destination_dir:, bucket:, cache_control: 3600)
|
59
|
+
raise(Uninitialised, "gsutil has not been properly set up!") unless valid?
|
60
|
+
|
61
|
+
log_debug("Uploading '#{source_dir}' using gsutil to bucket '#{bucket}' with destination '#{destination_dir}'")
|
62
|
+
with_credentials do |key_file|
|
63
|
+
execute_shell([
|
64
|
+
base_cmd(key_file),
|
65
|
+
"-h 'Cache-Control:private, max-age=#{cache_control}'",
|
66
|
+
"rsync -r #{source_dir} gs://#{bucket}/#{destination_dir}"
|
67
|
+
].join(" "))
|
68
|
+
end
|
69
|
+
log_debug("Finished upload successfully")
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Execute block with gcs credentials
|
75
|
+
#
|
76
|
+
# @return [void]
|
77
|
+
def with_credentials
|
78
|
+
if json_key[:file]
|
79
|
+
yield(json_key[:key])
|
80
|
+
else
|
81
|
+
Tempfile.create("auth") do |f|
|
82
|
+
f.write(json_key[:key])
|
83
|
+
f.close
|
84
|
+
|
85
|
+
yield(f.path)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Google auth default credentials
|
91
|
+
#
|
92
|
+
# @return [String, Hash]
|
93
|
+
def gcs_credentials
|
94
|
+
@gcs_credentials ||= Google::Cloud::Storage.default_credentials
|
95
|
+
end
|
96
|
+
|
97
|
+
# Google auth json key
|
98
|
+
#
|
99
|
+
# @return [Hash]
|
100
|
+
def json_key
|
101
|
+
@json_key ||= if gcs_credentials.is_a?(Hash)
|
102
|
+
{ file: false, key: gcs_credentials.to_json }
|
103
|
+
elsif gcs_credentials.is_a?(String) && File.exist?(gcs_credentials)
|
104
|
+
{ file: true, key: gcs_credentials.tap { |f| JSON.parse(File.read(f)) } }
|
105
|
+
else
|
106
|
+
raise(UnsupportedConfig, "only google key json credentials are supported for gsutil")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
alias check_credentials json_key
|
110
|
+
|
111
|
+
# Base command
|
112
|
+
#
|
113
|
+
# @param [String] key_file
|
114
|
+
# @return [String]
|
115
|
+
def base_cmd(key_file)
|
116
|
+
"gsutil -o 'Credentials:gs_service_key_file=#{key_file}' -m"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -1,28 +1,66 @@
|
|
1
1
|
require "pastel"
|
2
2
|
require "open3"
|
3
|
+
require "logger"
|
4
|
+
require "stringio"
|
3
5
|
|
4
6
|
module Publisher
|
5
7
|
# Helpers
|
6
8
|
#
|
7
9
|
module Helpers
|
8
|
-
|
9
|
-
#
|
10
|
-
# @param [Boolean] force_color
|
11
|
-
# @return [Pastel]
|
12
|
-
def self.pastel(force_color: nil)
|
13
|
-
@pastel ||= Pastel.new(enabled: force_color, eachline: "\n")
|
14
|
-
end
|
10
|
+
class ShellCommandFailure < StandardError; end
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
class << self
|
13
|
+
# Global instance of pastel
|
14
|
+
#
|
15
|
+
# @param [Boolean] force_color
|
16
|
+
# @return [Pastel]
|
17
|
+
def pastel(force_color: nil)
|
18
|
+
@pastel ||= Pastel.new(enabled: force_color, eachline: "\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
# Check allure cli is installed and executable
|
22
|
+
#
|
23
|
+
# @return [void]
|
24
|
+
def allure_cli?
|
25
|
+
execute_shell("which allure")
|
26
|
+
rescue StandardError
|
27
|
+
Helpers.error(
|
28
|
+
"Allure cli is missing! See https://docs.qameta.io/allure/#_installing_a_commandline on how to install it!"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if gsutil is installed and executable
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def gsutil?
|
36
|
+
execute_shell("which gsutil") && true
|
37
|
+
rescue StandardError
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
# Debug logging session output
|
42
|
+
#
|
43
|
+
# @return [StringIO]
|
44
|
+
def debug_io
|
45
|
+
@debug_io ||= StringIO.new
|
46
|
+
end
|
47
|
+
|
48
|
+
# Clear debug log output
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
def reset_debug_io!
|
52
|
+
@debug_io = nil
|
53
|
+
end
|
22
54
|
|
23
|
-
|
24
|
-
|
25
|
-
|
55
|
+
# Logger instance
|
56
|
+
#
|
57
|
+
# @return [Logger]
|
58
|
+
def logger
|
59
|
+
Logger.new(debug_io).tap do |logger|
|
60
|
+
logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
61
|
+
logger.formatter = proc { |_severity, time, _progname, msg| "[#{time}] #{msg}\n" }
|
62
|
+
end
|
63
|
+
end
|
26
64
|
end
|
27
65
|
|
28
66
|
# Colorize string
|
@@ -43,6 +81,14 @@ module Publisher
|
|
43
81
|
puts colorize(message, color)
|
44
82
|
end
|
45
83
|
|
84
|
+
# Save debug message to be displayed later
|
85
|
+
#
|
86
|
+
# @param [String] message
|
87
|
+
# @return [void]
|
88
|
+
def log_debug(message)
|
89
|
+
Helpers.logger.info(message)
|
90
|
+
end
|
91
|
+
|
46
92
|
# Print error message and exit
|
47
93
|
#
|
48
94
|
# @param [String] message
|
@@ -64,13 +110,25 @@ module Publisher
|
|
64
110
|
#
|
65
111
|
# @param [String] command
|
66
112
|
# @return [String] output
|
67
|
-
def execute_shell(command)
|
113
|
+
def execute_shell(command, mask: nil)
|
114
|
+
loggable_command = mask ? command.gsub(mask, "***") : command
|
115
|
+
log_debug("Executing command '#{loggable_command}'")
|
68
116
|
out, err, status = Open3.capture3(command)
|
69
|
-
raise("Out:\n#{out}\n\nErr:\n#{err}") unless status.success?
|
70
117
|
|
71
|
-
|
118
|
+
cmd_output = []
|
119
|
+
cmd_output << "Out: #{out}" unless out.empty?
|
120
|
+
cmd_output << "Err: #{err}" unless err.empty?
|
121
|
+
output = cmd_output.join("\n")
|
122
|
+
|
123
|
+
unless status.success?
|
124
|
+
err_msg = "Command '#{loggable_command}' failed!\n#{output}"
|
125
|
+
err_msg = mask ? err_msg.gsub(mask, "***") : err_msg
|
126
|
+
raise(ShellCommandFailure, err_msg)
|
127
|
+
end
|
128
|
+
|
129
|
+
mask ? output.gsub(mask, "***") : output
|
72
130
|
end
|
73
131
|
|
74
|
-
module_function :colorize, :log, :error, :path, :execute_shell
|
132
|
+
module_function :colorize, :log, :log_debug, :error, :path, :execute_shell
|
75
133
|
end
|
76
134
|
end
|
@@ -9,9 +9,12 @@ module Publisher
|
|
9
9
|
class Spinner
|
10
10
|
include Helpers
|
11
11
|
|
12
|
-
|
12
|
+
class Failure < StandardError; end
|
13
|
+
|
14
|
+
def initialize(spinner_message, exit_on_error: true, debug: false)
|
13
15
|
@spinner_message = spinner_message
|
14
16
|
@exit_on_error = exit_on_error
|
17
|
+
@debug = debug
|
15
18
|
end
|
16
19
|
|
17
20
|
# Run code block inside spinner
|
@@ -21,8 +24,8 @@ module Publisher
|
|
21
24
|
# @param [Boolean] exit_on_error
|
22
25
|
# @param [Proc] &block
|
23
26
|
# @return [void]
|
24
|
-
def self.spin(spinner_message, done_message: "done", exit_on_error: true, &block)
|
25
|
-
new(spinner_message, exit_on_error: exit_on_error).spin(done_message, &block)
|
27
|
+
def self.spin(spinner_message, done_message: "done", exit_on_error: true, debug: false, &block)
|
28
|
+
new(spinner_message, exit_on_error: exit_on_error, debug: debug).spin(done_message, &block)
|
26
29
|
end
|
27
30
|
|
28
31
|
# Run code block inside spinner
|
@@ -34,14 +37,32 @@ module Publisher
|
|
34
37
|
yield
|
35
38
|
spinner_success(done_message)
|
36
39
|
rescue StandardError => e
|
37
|
-
spinner_error(e
|
38
|
-
|
40
|
+
spinner_error(e)
|
41
|
+
raise(Failure, e.message) if exit_on_error
|
42
|
+
ensure
|
43
|
+
print_debug
|
44
|
+
Helpers.reset_debug_io!
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
42
48
|
private
|
43
49
|
|
44
|
-
attr_reader :spinner_message,
|
50
|
+
attr_reader :spinner_message,
|
51
|
+
:exit_on_error,
|
52
|
+
:debug
|
53
|
+
|
54
|
+
# Print debug contents
|
55
|
+
#
|
56
|
+
# @return [void]
|
57
|
+
def print_debug
|
58
|
+
return if !debug || Helpers.debug_io.string.empty?
|
59
|
+
|
60
|
+
puts <<~OUT.strip
|
61
|
+
== DEBUG LOG OUTPUT ==
|
62
|
+
#{Helpers.debug_io.string.strip}
|
63
|
+
== DEBUG LOG OUTPUT ==
|
64
|
+
OUT
|
65
|
+
end
|
45
66
|
|
46
67
|
# Error message color
|
47
68
|
#
|
@@ -96,10 +117,12 @@ module Publisher
|
|
96
117
|
|
97
118
|
# Return spinner error
|
98
119
|
#
|
99
|
-
# @param [
|
120
|
+
# @param [StandardError] error
|
100
121
|
# @return [void]
|
101
|
-
def spinner_error(
|
102
|
-
|
122
|
+
def spinner_error(error)
|
123
|
+
message = ["failed", error.message]
|
124
|
+
message << error.backtrace if debug
|
125
|
+
colored_message = colorize(message.compact.join("\n"), error_color)
|
103
126
|
return spinner.error(colored_message) if tty?
|
104
127
|
|
105
128
|
spinner.stop
|
@@ -70,7 +70,7 @@ module Publisher
|
|
70
70
|
# Short summary table
|
71
71
|
#
|
72
72
|
# @return [Array<String>]
|
73
|
-
def short_summary
|
73
|
+
def short_summary
|
74
74
|
return @short_summary if defined?(@short_summary)
|
75
75
|
|
76
76
|
sum = summary_data.values.each_with_object({
|
@@ -5,6 +5,8 @@ module Publisher
|
|
5
5
|
# Github implementation
|
6
6
|
#
|
7
7
|
class Github < Provider
|
8
|
+
include Helpers
|
9
|
+
|
8
10
|
# Set octokit to autopaginate
|
9
11
|
#
|
10
12
|
Octokit.configure do |config|
|
@@ -60,6 +62,7 @@ module Publisher
|
|
60
62
|
def update_pr_description
|
61
63
|
return File.write(step_summary_file, url_section_builder.comment_body) if actions?
|
62
64
|
|
65
|
+
log_debug("Updating pr description for pr !#{pr_id}")
|
63
66
|
client.update_pull_request(repository, pr_id, body: url_section_builder.updated_pr_description(pr_description))
|
64
67
|
end
|
65
68
|
|
@@ -67,8 +70,12 @@ module Publisher
|
|
67
70
|
#
|
68
71
|
# @return [void]
|
69
72
|
def add_comment
|
70
|
-
|
73
|
+
unless comment
|
74
|
+
log_debug("Creating comment with summary for pr ! #{pr_id}")
|
75
|
+
return client.add_comment(repository, pr_id, url_section_builder.comment_body)
|
76
|
+
end
|
71
77
|
|
78
|
+
log_debug("Updating summary in comment with id #{comment[:id]} in pr !#{pr_id}")
|
72
79
|
client.update_comment(repository, comment[:id], url_section_builder.comment_body(comment[:body]))
|
73
80
|
end
|
74
81
|
|
@@ -5,6 +5,8 @@ module Publisher
|
|
5
5
|
# Gitlab implementation
|
6
6
|
#
|
7
7
|
class Gitlab < Provider
|
8
|
+
include Helpers
|
9
|
+
|
8
10
|
# Get ci run ID without creating instance of ci provider
|
9
11
|
#
|
10
12
|
# @return [String]
|
@@ -48,6 +50,7 @@ module Publisher
|
|
48
50
|
#
|
49
51
|
# @return [void]
|
50
52
|
def update_pr_description
|
53
|
+
log_debug("Updating mr description for mr !#{mr_iid}")
|
51
54
|
client.update_merge_request(
|
52
55
|
project,
|
53
56
|
mr_iid,
|
@@ -59,8 +62,12 @@ module Publisher
|
|
59
62
|
#
|
60
63
|
# @return [void]
|
61
64
|
def add_comment
|
62
|
-
|
65
|
+
unless comment
|
66
|
+
log_debug("Creating comment with summary for mr ! #{mr_iid}")
|
67
|
+
return client.create_merge_request_comment(project, mr_iid, url_section_builder.comment_body)
|
68
|
+
end
|
63
69
|
|
70
|
+
log_debug("Updating summary in comment with id #{comment.id} in mr !#{mr_iid}")
|
64
71
|
client.edit_merge_request_note(project, mr_iid, comment.id, url_section_builder.comment_body(comment.body))
|
65
72
|
end
|
66
73
|
|
@@ -18,6 +18,8 @@ module Publisher
|
|
18
18
|
#
|
19
19
|
# @return [void]
|
20
20
|
def generate
|
21
|
+
create_common_path
|
22
|
+
|
21
23
|
generate_report
|
22
24
|
end
|
23
25
|
|
@@ -25,14 +27,17 @@ module Publisher
|
|
25
27
|
#
|
26
28
|
# @return [String]
|
27
29
|
def common_info_path
|
28
|
-
@common_info_path ||= Dir.mktmpdir("allure-results")
|
30
|
+
@common_info_path ||= Dir.mktmpdir("allure-results").tap do |path|
|
31
|
+
log_debug("Created tmp folder for common data: '#{path}'")
|
32
|
+
end
|
29
33
|
end
|
34
|
+
alias create_common_path common_info_path
|
30
35
|
|
31
36
|
# Allure report directory
|
32
37
|
#
|
33
38
|
# @return [String]
|
34
39
|
def report_path
|
35
|
-
@report_path ||= Dir.
|
40
|
+
@report_path ||= File.join(Dir.tmpdir, "allure-report-#{Time.now.to_i}")
|
36
41
|
end
|
37
42
|
|
38
43
|
private
|
@@ -55,10 +60,12 @@ module Publisher
|
|
55
60
|
#
|
56
61
|
# @return [void]
|
57
62
|
def generate_report
|
58
|
-
|
59
|
-
|
60
|
-
)
|
61
|
-
|
63
|
+
log_debug("Generating allure report from following paths: #{result_paths}")
|
64
|
+
cmd = "allure generate --clean --output #{report_path} #{common_info_path} #{result_paths}"
|
65
|
+
out = execute_shell(cmd)
|
66
|
+
log_debug("Generated allure report. #{out}")
|
67
|
+
rescue StandardError => e
|
68
|
+
raise(AllureError, e.message)
|
62
69
|
end
|
63
70
|
end
|
64
71
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "forwardable"
|
2
|
+
require "json"
|
2
3
|
|
3
4
|
module Publisher
|
4
5
|
module Uploaders
|
@@ -73,6 +74,7 @@ module Publisher
|
|
73
74
|
def add_result_summary
|
74
75
|
return unless update_pr && ci_provider
|
75
76
|
|
77
|
+
log_debug("Adding test result summary")
|
76
78
|
ci_provider.add_result_summary
|
77
79
|
end
|
78
80
|
|
@@ -214,7 +216,8 @@ module Publisher
|
|
214
216
|
def add_history
|
215
217
|
create_history_dir
|
216
218
|
download_history
|
217
|
-
rescue HistoryNotFoundError
|
219
|
+
rescue HistoryNotFoundError => e
|
220
|
+
log_debug(e.message)
|
218
221
|
nil
|
219
222
|
end
|
220
223
|
|
@@ -224,7 +227,11 @@ module Publisher
|
|
224
227
|
def add_executor_info
|
225
228
|
return unless ci_provider
|
226
229
|
|
227
|
-
|
230
|
+
json_path = "#{common_info_path}/#{EXECUTOR_JSON}"
|
231
|
+
json = ci_provider.executor_info.to_json
|
232
|
+
log_debug("Saving ci executor info")
|
233
|
+
File.write(json_path, json)
|
234
|
+
log_debug("Saved '#{EXECUTOR_JSON}' as '#{json_path}'\n#{JSON.pretty_generate(ci_provider.executor_info)}")
|
228
235
|
end
|
229
236
|
|
230
237
|
# Run upload commands
|
@@ -240,7 +247,8 @@ module Publisher
|
|
240
247
|
#
|
241
248
|
# @return [void]
|
242
249
|
def create_history_dir
|
243
|
-
FileUtils.mkdir_p(path(common_info_path, "history"))
|
250
|
+
path = FileUtils.mkdir_p(path(common_info_path, "history"))
|
251
|
+
log_debug("Created tmp folder for history data: '#{path.first}'")
|
244
252
|
end
|
245
253
|
end
|
246
254
|
end
|
@@ -14,6 +14,13 @@ module Publisher
|
|
14
14
|
@client ||= Google::Cloud::Storage.new
|
15
15
|
end
|
16
16
|
|
17
|
+
# Gsutil class
|
18
|
+
#
|
19
|
+
# @return [Helpers::Gsutil]
|
20
|
+
def gsutil
|
21
|
+
@gsutil ||= Helpers::Gsutil.init
|
22
|
+
end
|
23
|
+
|
17
24
|
# GCS bucket
|
18
25
|
#
|
19
26
|
# @return [Google::Cloud::Storage::Bucket]
|
@@ -39,11 +46,14 @@ module Publisher
|
|
39
46
|
#
|
40
47
|
# @return [void]
|
41
48
|
def download_history
|
49
|
+
log_debug("Downloading previous run history")
|
42
50
|
HISTORY.each do |file_name|
|
43
51
|
file = bucket.file(key(prefix, "history", file_name))
|
44
52
|
raise(HistoryNotFoundError, "Allure history from previous runs not found!") unless file
|
45
53
|
|
46
|
-
|
54
|
+
file_path = path(common_info_path, "history", file_name)
|
55
|
+
file.download(file_path)
|
56
|
+
log_debug("Downloaded '#{file_name}' as '#{file_path}'")
|
47
57
|
end
|
48
58
|
end
|
49
59
|
|
@@ -51,6 +61,7 @@ module Publisher
|
|
51
61
|
#
|
52
62
|
# @return [void]
|
53
63
|
def upload_history
|
64
|
+
log_debug("Uploading report history")
|
54
65
|
upload_to_gcs(report_files.select { |file| file.fnmatch?("*/history/*") }, prefix)
|
55
66
|
end
|
56
67
|
|
@@ -58,6 +69,9 @@ module Publisher
|
|
58
69
|
#
|
59
70
|
# @return [void]
|
60
71
|
def upload_report
|
72
|
+
log_debug("Uploading report files")
|
73
|
+
return batch_upload_to_gcs(report_path, full_prefix) if gsutil.valid?
|
74
|
+
|
61
75
|
upload_to_gcs(report_files, full_prefix)
|
62
76
|
end
|
63
77
|
|
@@ -65,6 +79,9 @@ module Publisher
|
|
65
79
|
#
|
66
80
|
# @return [void]
|
67
81
|
def upload_latest_copy
|
82
|
+
log_debug("Uploading report copy as latest report")
|
83
|
+
return batch_upload_to_gcs(report_path, prefix, cache_control: 60) if gsutil.valid?
|
84
|
+
|
68
85
|
upload_to_gcs(report_files, prefix, cache_control: 60)
|
69
86
|
end
|
70
87
|
|
@@ -73,8 +90,9 @@ module Publisher
|
|
73
90
|
# @param [Array<Pathname>] files
|
74
91
|
# @param [String] key_prefix
|
75
92
|
# @param [Hash] params
|
76
|
-
# @return [
|
93
|
+
# @return [void]
|
77
94
|
def upload_to_gcs(files, key_prefix, cache_control: 3600)
|
95
|
+
threads = 8
|
78
96
|
args = files.map do |file|
|
79
97
|
{
|
80
98
|
file: file.to_s,
|
@@ -83,9 +101,25 @@ module Publisher
|
|
83
101
|
}
|
84
102
|
end
|
85
103
|
|
86
|
-
|
104
|
+
log_debug("Uploading '#{args.size}' files in '#{threads}' threads to bucker '#{bucket_name}'")
|
105
|
+
Parallel.each(args, in_threads: threads) do |obj|
|
87
106
|
bucket.create_file(*obj.slice(:file, :path).values, **obj.slice(:cache_control))
|
88
107
|
end
|
108
|
+
log_debug("Finished upload successfully")
|
109
|
+
end
|
110
|
+
|
111
|
+
# Batch upload whole directory
|
112
|
+
#
|
113
|
+
# @param [String] source_dir
|
114
|
+
# @param [String] destination_dir
|
115
|
+
# @return [void]
|
116
|
+
def batch_upload_to_gcs(source_dir, destination_dir, cache_control: 3600)
|
117
|
+
gsutil.batch_copy(
|
118
|
+
source_dir: source_dir,
|
119
|
+
destination_dir: destination_dir,
|
120
|
+
bucket: bucket_name,
|
121
|
+
cache_control: cache_control
|
122
|
+
)
|
89
123
|
end
|
90
124
|
|
91
125
|
# Fabricate key for s3 object
|
@@ -47,12 +47,15 @@ module Publisher
|
|
47
47
|
#
|
48
48
|
# @return [void]
|
49
49
|
def download_history
|
50
|
-
|
50
|
+
log_debug("Downloading previous run history")
|
51
|
+
HISTORY.each do |file_name|
|
52
|
+
file_path = path(common_info_path, "history", file_name)
|
51
53
|
client.get_object(
|
52
|
-
response_target:
|
53
|
-
key: key(prefix, "history",
|
54
|
+
response_target: file_path,
|
55
|
+
key: key(prefix, "history", file_name),
|
54
56
|
bucket: bucket_name
|
55
57
|
)
|
58
|
+
log_debug("Downloaded '#{file_name}' as '#{file_path}'")
|
56
59
|
end
|
57
60
|
rescue Aws::S3::Errors::NoSuchKey
|
58
61
|
raise(HistoryNotFoundError, "Allure history from previous runs not found!")
|
@@ -62,6 +65,7 @@ module Publisher
|
|
62
65
|
#
|
63
66
|
# @return [void]
|
64
67
|
def upload_history
|
68
|
+
log_debug("Uploading report history")
|
65
69
|
upload_to_s3(report_files.select { |file| file.fnmatch?("*/history/*") }, prefix)
|
66
70
|
end
|
67
71
|
|
@@ -69,6 +73,7 @@ module Publisher
|
|
69
73
|
#
|
70
74
|
# @return [void]
|
71
75
|
def upload_report
|
76
|
+
log_debug("Uploading report files")
|
72
77
|
upload_to_s3(report_files, full_prefix)
|
73
78
|
end
|
74
79
|
|
@@ -76,6 +81,7 @@ module Publisher
|
|
76
81
|
#
|
77
82
|
# @return [void]
|
78
83
|
def upload_latest_copy
|
84
|
+
log_debug("Uploading report copy as latest report")
|
79
85
|
upload_to_s3(report_files, prefix, cache_control: 60)
|
80
86
|
end
|
81
87
|
|
@@ -85,6 +91,7 @@ module Publisher
|
|
85
91
|
# @param [String] key_prefix
|
86
92
|
# @return [Array<Hash>]
|
87
93
|
def upload_to_s3(files, key_prefix, cache_control: 3600)
|
94
|
+
threads = 8
|
88
95
|
args = files.map do |file|
|
89
96
|
{
|
90
97
|
body: File.new(file),
|
@@ -95,7 +102,9 @@ module Publisher
|
|
95
102
|
}
|
96
103
|
end
|
97
104
|
|
98
|
-
|
105
|
+
log_debug("Uploading '#{args.size}' files in '#{threads}' threads")
|
106
|
+
Parallel.each(args, in_threads: threads) { |obj| client.put_object(obj) }
|
107
|
+
log_debug("Finished upload successfully")
|
99
108
|
end
|
100
109
|
|
101
110
|
# Fabricate key for s3 object
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: allure-report-publisher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrejs Cunskis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-s3
|
@@ -202,6 +202,20 @@ dependencies:
|
|
202
202
|
- - "~>"
|
203
203
|
- !ruby/object:Gem::Version
|
204
204
|
version: 0.9.3
|
205
|
+
- !ruby/object:Gem::Dependency
|
206
|
+
name: activesupport
|
207
|
+
requirement: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - "~>"
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '7.0'
|
212
|
+
type: :development
|
213
|
+
prerelease: false
|
214
|
+
version_requirements: !ruby/object:Gem::Requirement
|
215
|
+
requirements:
|
216
|
+
- - "~>"
|
217
|
+
- !ruby/object:Gem::Version
|
218
|
+
version: '7.0'
|
205
219
|
- !ruby/object:Gem::Dependency
|
206
220
|
name: allure-rspec
|
207
221
|
requirement: !ruby/object:Gem::Requirement
|
@@ -231,19 +245,19 @@ dependencies:
|
|
231
245
|
- !ruby/object:Gem::Version
|
232
246
|
version: 1.2.0
|
233
247
|
- !ruby/object:Gem::Dependency
|
234
|
-
name:
|
248
|
+
name: debug
|
235
249
|
requirement: !ruby/object:Gem::Requirement
|
236
250
|
requirements:
|
237
|
-
- - "
|
251
|
+
- - ">="
|
238
252
|
- !ruby/object:Gem::Version
|
239
|
-
version:
|
253
|
+
version: 1.0.0
|
240
254
|
type: :development
|
241
255
|
prerelease: false
|
242
256
|
version_requirements: !ruby/object:Gem::Requirement
|
243
257
|
requirements:
|
244
|
-
- - "
|
258
|
+
- - ">="
|
245
259
|
- !ruby/object:Gem::Version
|
246
|
-
version:
|
260
|
+
version: 1.0.0
|
247
261
|
- !ruby/object:Gem::Dependency
|
248
262
|
name: rake
|
249
263
|
requirement: !ruby/object:Gem::Requirement
|
@@ -362,14 +376,14 @@ dependencies:
|
|
362
376
|
requirements:
|
363
377
|
- - "~>"
|
364
378
|
- !ruby/object:Gem::Version
|
365
|
-
version: 0.
|
379
|
+
version: 0.47.0
|
366
380
|
type: :development
|
367
381
|
prerelease: false
|
368
382
|
version_requirements: !ruby/object:Gem::Requirement
|
369
383
|
requirements:
|
370
384
|
- - "~>"
|
371
385
|
- !ruby/object:Gem::Version
|
372
|
-
version: 0.
|
386
|
+
version: 0.47.0
|
373
387
|
description: Upload allure reports to different file storage providers
|
374
388
|
email:
|
375
389
|
- andrejs.cunskis@gmail.com
|
@@ -383,6 +397,7 @@ files:
|
|
383
397
|
- lib/allure_report_publisher.rb
|
384
398
|
- lib/allure_report_publisher/commands/upload.rb
|
385
399
|
- lib/allure_report_publisher/commands/version.rb
|
400
|
+
- lib/allure_report_publisher/lib/helpers/gsutil.rb
|
386
401
|
- lib/allure_report_publisher/lib/helpers/helpers.rb
|
387
402
|
- lib/allure_report_publisher/lib/helpers/spinner.rb
|
388
403
|
- lib/allure_report_publisher/lib/helpers/summary.rb
|