allure-report-publisher 1.0.0 → 1.2.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.
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