allure-report-publisher 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbc236368c7b0893b5de931e7ecd37836c72bc540a5909efcf319fd74348541a
4
- data.tar.gz: bf74a63cf06f76bc27d52cdf16fce99006a82076c12fce25de89375a27a932c0
3
+ metadata.gz: 1809950b5cb79015f9d4568c28eea62fea2ff4abb581ee762303684aa86859a2
4
+ data.tar.gz: e22f93a0a820aded1cd74bef9b3836867e6c25a0a454acd57343159ba19c780f
5
5
  SHA512:
6
- metadata.gz: 51104eb7d7073e3495804534aaa0f415cec9f9300dd1c0fa8e1338b065acae290f91fd758c61e0af45f34165f1710923bd6a6e0f2b6d1a9c4d295d409fa89909
7
- data.tar.gz: 5aec20910db4609549224a161a09502a390be539a77873b2d1de556b73d307b23d93d0d0f6c8404897569c7fc84e4a7c366bb33cfb6dc40406c530f1874cd2c6
6
+ metadata.gz: 7695a8fab036e58f775d1d6372ddcc054cd791212e278c840fb2642e4c4c29fb35409fe5cf9e30475217be471f564e68c231362305e89f3815bdb31c53f05ec1
7
+ data.tar.gz: c9a64aaf86756c14c2e735a9632a1024d178ea1b8e9e97a43d65ad2afd05ff4adaa38094a9f12e083a70d145006269defdb1252ef64991e6412913cd47e6ea5e
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",
@@ -66,18 +70,15 @@ module Publisher
66
70
  @args = args
67
71
 
68
72
  validate_args
69
- validate_result_files
70
-
71
- log("Generating allure report")
72
- Spinner.spin("generating") { uploader.generate_report }
73
+ scan_results_paths
73
74
 
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
- log("Adding reports urls")
80
- Spinner.spin("updating", exit_on_error: false) { uploader.add_result_summary }
79
+ add_report_urls
80
+ rescue StandardError => e
81
+ handle_error(e)
81
82
  end
82
83
 
83
84
  private
@@ -90,8 +91,8 @@ module Publisher
90
91
  def uploader
91
92
  @uploader ||= uploaders(args[:type]).new(
92
93
  summary_type: args[:summary],
94
+ result_paths: @result_paths,
93
95
  **args.slice(
94
- :results_glob,
95
96
  :bucket,
96
97
  :prefix,
97
98
  :copy_latest,
@@ -121,18 +122,54 @@ module Publisher
121
122
  error("Missing argument --bucket!") unless args[:bucket]
122
123
  end
123
124
 
124
- # Check if allure results present
125
+ # Scan for allure results paths
125
126
  #
126
127
  # @param [String] results_glob
127
128
  # @return [void]
128
- def validate_result_files
129
+ def scan_results_paths
129
130
  results_glob = args[:results_glob]
130
131
  ignore = args[:ignore_missing_results]
131
- return unless Dir.glob(results_glob).empty?
132
+ @result_paths = Dir.glob(results_glob)
133
+ log_debug("Glob '#{results_glob}' found #{@result_paths.size} paths")
134
+ return unless @result_paths.empty?
132
135
 
133
- log("Glob '#{results_glob}' did not match any files!", ignore ? :yellow : :red)
136
+ log("Glob '#{results_glob}' did not match any paths!", ignore ? :yellow : :red)
134
137
  exit(ignore ? 0 : 1)
135
138
  end
139
+
140
+ # Generate allure report
141
+ #
142
+ # @return [void]
143
+ def generate_report
144
+ log("Generating allure report")
145
+ Spinner.spin("generating", debug: args[:debug]) { uploader.generate_report }
146
+ end
147
+
148
+ # Upload report to cloud storage
149
+ #
150
+ # @return [void]
151
+ def upload_report
152
+ log("Uploading allure report to #{args[:type]}")
153
+ Spinner.spin("uploading", debug: args[:debug]) { uploader.upload }
154
+ uploader.report_urls.each { |k, v| log("#{k}: #{v}", :green) }
155
+ end
156
+
157
+ # Add report results to pr/mr
158
+ #
159
+ # @return [void]
160
+ def add_report_urls
161
+ log("Adding reports urls")
162
+ Spinner.spin("updating", exit_on_error: false, debug: args[:debug]) { uploader.add_result_summary }
163
+ end
164
+
165
+ # Handle error during upload command
166
+ #
167
+ # @param [StandardError] error
168
+ # @return [void]
169
+ def handle_error(error)
170
+ exit(1) if error.is_a?(Spinner::Failure)
171
+ error(error)
172
+ end
136
173
  end
137
174
  end
138
175
  end
@@ -0,0 +1,141 @@
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 copy operation within a single bucket
52
+ #
53
+ # @param [String] source_dir
54
+ # @param [String] destination_dir
55
+ # @param [String] bucket
56
+ # @param [Integer] cache_control
57
+ # @return [void]
58
+ def batch_copy(source_dir:, destination_dir:, bucket:, cache_control: 3600)
59
+ batch_upload(
60
+ source_dir: "gs://#{bucket}/#{source_dir}",
61
+ destination_dir: destination_dir,
62
+ bucket: bucket,
63
+ cache_control: cache_control
64
+ )
65
+ end
66
+
67
+ # Perform batch upload operation
68
+ #
69
+ # @param [String] source_dir
70
+ # @param [String] destination_dir
71
+ # @param [String] bucket
72
+ # @param [String] cache_control
73
+ # @return [void]
74
+ def batch_upload(source_dir:, destination_dir:, bucket:, cache_control: 3600)
75
+ raise(Uninitialised, "gsutil has not been properly set up!") unless valid?
76
+
77
+ action = source_dir.start_with?("gs://") ? "Copying" : "Uploading"
78
+ destination = "gs://#{bucket}/#{destination_dir}"
79
+
80
+ log_debug("#{action} '#{source_dir}' to '#{destination}'")
81
+ with_credentials do |key_file|
82
+ execute_shell([
83
+ base_cmd(key_file),
84
+ "-h 'Cache-Control:private, max-age=#{cache_control}'",
85
+ "rsync",
86
+ "-j json,csv,txt,js,css",
87
+ "-r #{source_dir} #{destination}"
88
+ ].join(" "))
89
+ end
90
+ log_debug("Finished upload successfully")
91
+ end
92
+
93
+ private
94
+
95
+ # Execute block with gcs credentials
96
+ #
97
+ # @return [void]
98
+ def with_credentials
99
+ if json_key[:file]
100
+ yield(json_key[:key])
101
+ else
102
+ Tempfile.create("auth") do |f|
103
+ f.write(json_key[:key])
104
+ f.close
105
+
106
+ yield(f.path)
107
+ end
108
+ end
109
+ end
110
+
111
+ # Google auth default credentials
112
+ #
113
+ # @return [String, Hash]
114
+ def gcs_credentials
115
+ @gcs_credentials ||= Google::Cloud::Storage.default_credentials
116
+ end
117
+
118
+ # Google auth json key
119
+ #
120
+ # @return [Hash]
121
+ def json_key
122
+ @json_key ||= if gcs_credentials.is_a?(Hash)
123
+ { file: false, key: gcs_credentials.to_json }
124
+ elsif gcs_credentials.is_a?(String) && File.exist?(gcs_credentials)
125
+ { file: true, key: gcs_credentials.tap { |f| JSON.parse(File.read(f)) } }
126
+ else
127
+ raise(UnsupportedConfig, "only google key json credentials are supported for gsutil")
128
+ end
129
+ end
130
+ alias check_credentials json_key
131
+
132
+ # Base command
133
+ #
134
+ # @param [String] key_file
135
+ # @return [String]
136
+ def base_cmd(key_file)
137
+ "gsutil -o 'Credentials:gs_service_key_file=#{key_file}' -m"
138
+ end
139
+ end
140
+ end
141
+ 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
- # Global instance of pastel
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
- # Check allure cli is installed and executable
17
- #
18
- # @return [void]
19
- def self.validate_allure_cli_present
20
- _out, status = Open3.capture2("which allure")
21
- return if status.success?
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
- Helpers.error(
24
- "Allure cli is missing! See https://docs.qameta.io/allure/#_installing_a_commandline on how to install it!"
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
- out
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
- def initialize(spinner_message, exit_on_error: true)
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.message)
38
- exit(1) if exit_on_error
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, :exit_on_error
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 [String] error_message
120
+ # @param [StandardError] error
100
121
  # @return [void]
101
- def spinner_error(error_message)
102
- colored_message = colorize("failed\n#{error_message}", error_color)
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 # rubocop:disable Metrics/MethodLength
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
- return client.add_comment(repository, pr_id, url_section_builder.comment_body) unless comment
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
- return client.create_merge_request_comment(project, mr_iid, url_section_builder.comment_body) unless comment
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
 
@@ -10,14 +10,16 @@ module Publisher
10
10
  class ReportGenerator
11
11
  include Helpers
12
12
 
13
- def initialize(results_glob)
14
- @results_glob = results_glob
13
+ def initialize(result_paths)
14
+ @result_paths = result_paths.join(" ")
15
15
  end
16
16
 
17
17
  # Generate allure report
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,40 +27,34 @@ 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.mktmpdir("allure-report")
40
+ @report_path ||= File.join(Dir.tmpdir, "allure-report-#{Time.now.to_i}")
36
41
  end
37
42
 
38
43
  private
39
44
 
40
- attr_reader :results_glob
41
-
42
- # Return all allure results paths from glob
43
- #
44
- # @return [String]
45
- def result_paths
46
- @result_paths ||= begin
47
- paths = Dir.glob(results_glob)
48
- raise(NoAllureResultsError, "Missing allure results") if paths.empty?
49
-
50
- paths.join(" ")
51
- end
52
- end
45
+ # @return [String] result paths string
46
+ attr_reader :result_paths
53
47
 
54
48
  # Generate allure report
55
49
  #
56
50
  # @return [void]
57
51
  def generate_report
58
- out, _err, status = Open3.capture3(
59
- "allure generate --clean --output #{report_path} #{common_info_path} #{result_paths}"
60
- )
61
- raise(AllureError, out) unless status.success?
52
+ log_debug("Generating allure report")
53
+ cmd = "allure generate --clean --output #{report_path} #{common_info_path} #{result_paths}"
54
+ out = execute_shell(cmd)
55
+ log_debug("Generated allure report. #{out}")
56
+ rescue StandardError => e
57
+ raise(AllureError, e.message)
62
58
  end
63
59
  end
64
60
  end
@@ -1,4 +1,5 @@
1
1
  require "forwardable"
2
+ require "json"
2
3
 
3
4
  module Publisher
4
5
  module Uploaders
@@ -22,7 +23,7 @@ module Publisher
22
23
  # Uploader instance
23
24
  #
24
25
  # @param [Hash] args
25
- # @option args [String] :results_glob
26
+ # @option args [Array] :result_paths
26
27
  # @option args [String] :bucket
27
28
  # @option args [String] :prefix
28
29
  # @option args [Boolean] :update_pr
@@ -31,7 +32,7 @@ module Publisher
31
32
  # @option args [Boolean] :collapse_summary
32
33
  # @option args [String] :copy_latest
33
34
  def initialize(**args)
34
- @results_glob = args[:results_glob]
35
+ @result_paths = args[:result_paths]
35
36
  @bucket_name = args[:bucket]
36
37
  @prefix = args[:prefix]
37
38
  @update_pr = args[:update_pr]
@@ -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
 
@@ -95,7 +97,7 @@ module Publisher
95
97
 
96
98
  private
97
99
 
98
- attr_reader :results_glob,
100
+ attr_reader :result_paths,
99
101
  :bucket_name,
100
102
  :prefix,
101
103
  :update_pr,
@@ -162,7 +164,7 @@ module Publisher
162
164
  #
163
165
  # @return [Publisher::ReportGenerator]
164
166
  def report_generator
165
- @report_generator ||= ReportGenerator.new(results_glob)
167
+ @report_generator ||= ReportGenerator.new(result_paths)
166
168
  end
167
169
 
168
170
  # Report path prefix
@@ -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
- File.write("#{common_info_path}/#{EXECUTOR_JSON}", ci_provider.executor_info.to_json)
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
- file.download(path(common_info_path, "history", file_name))
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,21 +61,28 @@ module Publisher
51
61
  #
52
62
  # @return [void]
53
63
  def upload_history
54
- upload_to_gcs(report_files.select { |file| file.fnmatch?("*/history/*") }, prefix)
64
+ log_debug("Uploading report history")
65
+ file_upload(report_files.select { |file| file.fnmatch?("*/history/*") }, prefix)
55
66
  end
56
67
 
57
68
  # Upload allure report
58
69
  #
59
70
  # @return [void]
60
71
  def upload_report
61
- upload_to_gcs(report_files, full_prefix)
72
+ log_debug("Uploading report files")
73
+ return batch_upload(report_path, full_prefix) if gsutil.valid?
74
+
75
+ file_upload(report_files, full_prefix)
62
76
  end
63
77
 
64
78
  # Upload copy of latest run
65
79
  #
66
80
  # @return [void]
67
81
  def upload_latest_copy
68
- upload_to_gcs(report_files, prefix, cache_control: 60)
82
+ log_debug("Uploading report copy as latest report")
83
+ return batch_copy(full_prefix, prefix, cache_control: 60) if gsutil.valid?
84
+
85
+ file_upload(report_files, prefix, cache_control: 60)
69
86
  end
70
87
 
71
88
  # Upload files to gcs
@@ -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 [Array<Hash>]
77
- def upload_to_gcs(files, key_prefix, cache_control: 3600)
93
+ # @return [void]
94
+ def file_upload(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,40 @@ module Publisher
83
101
  }
84
102
  end
85
103
 
86
- Parallel.each(args, in_threads: 8) do |obj|
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
+ # Upload directory recursively
112
+ #
113
+ # @param [String] source_dir
114
+ # @param [String] destination_dir
115
+ # @return [void]
116
+ def batch_upload(source_dir, destination_dir, cache_control: 3600)
117
+ gsutil.batch_upload(
118
+ source_dir: source_dir,
119
+ destination_dir: destination_dir,
120
+ bucket: bucket_name,
121
+ cache_control: cache_control
122
+ )
123
+ end
124
+
125
+ # Copy directory within the bucket
126
+ #
127
+ # @param [String] source_dir
128
+ # @param [String] destination_dir
129
+ # @param [String] cache_control
130
+ # @return [void]
131
+ def batch_copy(source_dir, destination_dir, cache_control: 3600)
132
+ gsutil.batch_copy(
133
+ source_dir: source_dir,
134
+ destination_dir: destination_dir,
135
+ bucket: bucket_name,
136
+ cache_control: cache_control
137
+ )
89
138
  end
90
139
 
91
140
  # Fabricate key for s3 object
@@ -47,12 +47,15 @@ module Publisher
47
47
  #
48
48
  # @return [void]
49
49
  def download_history
50
- HISTORY.each do |file|
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: path(common_info_path, "history", file),
53
- key: key(prefix, "history", file),
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
- Parallel.each(args, in_threads: 8) { |obj| client.put_object(obj) }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Publisher
4
- VERSION = "1.0.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -18,4 +18,4 @@ module Publisher
18
18
  end
19
19
  end
20
20
 
21
- Publisher::Commands.before("upload") { Publisher::Helpers.validate_allure_cli_present }
21
+ Publisher::Commands.before("upload") { Publisher::Helpers.allure_cli? }
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.0.0
4
+ version: 1.2.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-09-25 00:00:00.000000000 Z
11
+ date: 2022-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-s3
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 1.93.1
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 1.115.0
22
+ version: 1.116.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 1.93.1
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 1.115.0
32
+ version: 1.116.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: dry-cli
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -121,7 +121,7 @@ dependencies:
121
121
  version: '4.21'
122
122
  - - "<"
123
123
  - !ruby/object:Gem::Version
124
- version: '6.0'
124
+ version: '7.0'
125
125
  type: :runtime
126
126
  prerelease: false
127
127
  version_requirements: !ruby/object:Gem::Requirement
@@ -131,7 +131,7 @@ dependencies:
131
131
  version: '4.21'
132
132
  - - "<"
133
133
  - !ruby/object:Gem::Version
134
- version: '6.0'
134
+ version: '7.0'
135
135
  - !ruby/object:Gem::Dependency
136
136
  name: parallel
137
137
  requirement: !ruby/object:Gem::Requirement
@@ -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,25 @@ dependencies:
231
245
  - !ruby/object:Gem::Version
232
246
  version: 1.2.0
233
247
  - !ruby/object:Gem::Dependency
234
- name: pry-byebug
248
+ name: debug
235
249
  requirement: !ruby/object:Gem::Requirement
236
250
  requirements:
237
251
  - - "~>"
238
252
  - !ruby/object:Gem::Version
239
- version: '3.9'
253
+ version: '1.0'
254
+ - - ">="
255
+ - !ruby/object:Gem::Version
256
+ version: 1.0.0
240
257
  type: :development
241
258
  prerelease: false
242
259
  version_requirements: !ruby/object:Gem::Requirement
243
260
  requirements:
244
261
  - - "~>"
245
262
  - !ruby/object:Gem::Version
246
- version: '3.9'
263
+ version: '1.0'
264
+ - - ">="
265
+ - !ruby/object:Gem::Version
266
+ version: 1.0.0
247
267
  - !ruby/object:Gem::Dependency
248
268
  name: rake
249
269
  requirement: !ruby/object:Gem::Requirement
@@ -362,14 +382,14 @@ dependencies:
362
382
  requirements:
363
383
  - - "~>"
364
384
  - !ruby/object:Gem::Version
365
- version: 0.46.0
385
+ version: 0.47.0
366
386
  type: :development
367
387
  prerelease: false
368
388
  version_requirements: !ruby/object:Gem::Requirement
369
389
  requirements:
370
390
  - - "~>"
371
391
  - !ruby/object:Gem::Version
372
- version: 0.46.0
392
+ version: 0.47.0
373
393
  description: Upload allure reports to different file storage providers
374
394
  email:
375
395
  - andrejs.cunskis@gmail.com
@@ -383,6 +403,7 @@ files:
383
403
  - lib/allure_report_publisher.rb
384
404
  - lib/allure_report_publisher/commands/upload.rb
385
405
  - lib/allure_report_publisher/commands/version.rb
406
+ - lib/allure_report_publisher/lib/helpers/gsutil.rb
386
407
  - lib/allure_report_publisher/lib/helpers/helpers.rb
387
408
  - lib/allure_report_publisher/lib/helpers/spinner.rb
388
409
  - lib/allure_report_publisher/lib/helpers/summary.rb