kitchen-transport-express 1.3.0 → 1.4.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: b255fcbeed01b423bd2bd8e71069501b2c0b636e3d2313ccdd48f8347106333d
4
- data.tar.gz: 9696bbf314ccbda4c978cf89963e22005a18d064a359d835ccf033403d46f6b8
3
+ metadata.gz: e7399b24ea4426dcb6ea891466818b0a3471b24a6a67eb2163727afb8f9d7e35
4
+ data.tar.gz: fa22be3398901120654fe61679e913f3b714eaaa59f4c0c68bcd44bde1651410
5
5
  SHA512:
6
- metadata.gz: 548f04230a39e196b59fd952a4a5247bb9ee4c754d88785f61400565d7c0bd8802a1857a3c7896bb7f1a311bb63475c7416b7746768436eee60fb9cfa0b2dcc7
7
- data.tar.gz: ba37810bfcdafaa6ba2973e6410c35aac5ac57bc0f9102db149829cffadea1043a4a24c509f41801eacbc6347cec41ae66907c0b79d90d95484a8bc3ea4d3707
6
+ metadata.gz: 7b3e1b72d7b6f5117e286166e07092c83606965115650f6cf18949300280b1761e57ff78f43e375ce5785e317803c043b788b13167e6ea210ffec9c13294624c
7
+ data.tar.gz: 76f54ae47956867487ed15bea11dd1b114c5b92c6776da7efc9e44e4f2843837606c6fbf782ca3988fba4d62ffd3f2b5cf68ff74572e1ae0dbac9f8b35f2460e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # kitchen-transport-express CHANGELOG
2
2
 
3
+ ## 1.4.0
4
+ * feat: ♻️ utilize concurrent futures for enhanced parallelism
5
+ * feat: 🔊 enhanced logging
6
+
7
+ ## 1.3.1
8
+ * fix: 🐛 ensure directories that only contain dot files get archived
9
+
3
10
  ## 1.3.0
4
11
  * chore: 📝 minor updates to method documentation
5
12
  * chore: 🔧 add metadata to gemspec
@@ -30,39 +30,64 @@ module Kitchen
30
30
  def archive(path)
31
31
  archive_basename = ::File.basename(path) + ".tgz"
32
32
  archive_full_name = ::File.join(::File.dirname(path), archive_basename)
33
-
34
- file_count = ::Dir.glob(::File.join(path, "**/*")).size
35
- logger.debug("[#{Express::LOG_PREFIX}] #{path} contains #{file_count} files.")
36
- create_archive(path, archive_full_name)
33
+ files = all_files(path)
34
+ start_time = Time.now
35
+ create_archive(path, files, archive_full_name)
36
+ Express.log(logger, "create archive #{File.basename(archive_full_name)} (#{files.size} files)", start_time)
37
37
  archive_full_name
38
38
  end
39
39
 
40
+ # Transfers the archive to the remote host.
41
+ #
42
+ # @param session [Net::SSH::Connection::Session] the SSH session used to connect to the remote host and execute the extract and cleanup commands.
43
+ # @param local [String] the directory in the local sandbox that is being processed.
44
+ # @param remote [String] the remote directory (kitchen_root).
45
+ # @param opts [Hash] the ssh options that came in from the Kitchen instance.
46
+ def scp(session, local, remote, opts = {})
47
+ start_time = Time.now
48
+ session.scp.upload!(local, remote, opts)
49
+ Express.log(logger, "upload #{File.basename(local)} (Thread ID: #{Thread.current.object_id})", start_time)
50
+ end
51
+
40
52
  # Extracts the archive on the remote host.
41
53
  #
42
- # @param session [Net::SSH::Connection::Session] The SSH session used to connect to the remote host and execute the extract and cleanup commands.
54
+ # @param session [Net::SSH::Connection::Session] the SSH session used to connect to the remote host and execute the extract and cleanup commands.
55
+ # @param local [String] the directory in the local sandbox that is being processed.
56
+ # @param remote [String] the remote directory (kitchen_root).
43
57
  def extract(session, local, remote)
44
58
  return unless local.match(/.*\.tgz/)
45
59
 
60
+ start_time = Time.now
46
61
  archive_basename = File.basename(local)
47
- logger.debug("[#{Express::LOG_PREFIX}] Extracting #{::File.join(remote, archive_basename)}")
48
62
  session.open_channel do |channel|
49
63
  channel.request_pty
50
64
  channel.exec("tar -xzf #{::File.join(remote, archive_basename)} -C #{remote} && rm -f #{File.join(remote, archive_basename)}")
51
65
  end
52
66
  session.loop
67
+ Express.log(logger, "extract #{File.basename(local)} (Thread ID: #{Thread.current.object_id})", start_time)
53
68
  end
54
69
 
55
70
  private
56
71
 
72
+ # Creates a list of all files that are in the directory to be archived.
73
+ #
74
+ # @param path [String] the path to the directory that will be archived.
75
+ # @return [Array] an array of all files to be archived.
76
+ # @api private
77
+ def all_files(path)
78
+ Dir.glob(File.join(path, "/**/*"), File::FNM_DOTMATCH).reject { |f| %w{. ..}.include? File.basename(f) }
79
+ end
80
+
57
81
  # Creats a archive of the directory provided.
58
82
  #
59
83
  # @param path [String] the path to the directory that will be archived.
84
+ # @param files [Array] the array of all files that will be archived.
60
85
  # @param archive_path [String] the fully qualified path to the archive that will be created.
61
86
  # @api private
62
- def create_archive(path, archive_path)
87
+ def create_archive(path, files, archive_path)
63
88
  Archive.write_open_filename(archive_path, Archive::COMPRESSION_GZIP,
64
89
  Archive::FORMAT_TAR_PAX_RESTRICTED) do |tar|
65
- write_content(tar, path)
90
+ write_content(tar, path, files)
66
91
  end
67
92
  end
68
93
 
@@ -70,10 +95,10 @@ module Kitchen
70
95
  #
71
96
  # @param tar [Archive::Writer] the instance of the archive class.
72
97
  # @param path [String] the path to the directory that will be archived.
98
+ # @param files [Array] the array of all files that will be archived.
73
99
  # @api private
74
- def write_content(tar, path)
75
- all_files = Dir.glob("#{path}/**/*")
76
- all_files.each do |f|
100
+ def write_content(tar, path, files)
101
+ files.each do |f|
77
102
  if File.file? f
78
103
  tar.new_entry do |e|
79
104
  entry(e, f, path)
@@ -20,7 +20,7 @@ module Kitchen
20
20
  # The version string for Kitchen Transport Express.
21
21
  #
22
22
  # @author Justin Steele <justin.steele@oracle.com>
23
- VERSION = "1.3.0"
23
+ VERSION = "1.4.0"
24
24
  end
25
25
  end
26
26
  end
@@ -27,6 +27,16 @@ module Kitchen
27
27
  class Express
28
28
  # A constant that gets prepended to debugger messages.
29
29
  LOG_PREFIX = "EXPRESS"
30
+
31
+ # Logger class method to unify logging.
32
+ #
33
+ # @param logger [Kitchen::Logger] the logger that was created by the kitchen instance.
34
+ # @param message [String] the message to output.
35
+ # @param start_time [Time] the start time of the process if duration is desired to be part of the message.
36
+ def self.log(logger, message = nil, start_time = nil)
37
+ message = "#{message} (#{Time.now - start_time}s)" if start_time
38
+ logger.debug "[#{Express::LOG_PREFIX}] [#{Time.now.getutc.strftime("%Y-%m-%dT%H:%M:%S%:z")}] #{message}"
39
+ end
30
40
  end
31
41
 
32
42
  # Express SSH Transport Error class.
@@ -51,7 +61,7 @@ module Kitchen
51
61
  # @return [Ssh::Connection] an instance of Kitchen::Transport::ExpressSsh::Connection.
52
62
  def create_new_connection(options, &block)
53
63
  if @connection
54
- logger.debug("[#{Express::LOG_PREFIX}] Shutting previous connection #{@connection}")
64
+ Express.log(logger, "shutting previous connection #{@connection}")
55
65
  @connection.close
56
66
  end
57
67
 
@@ -95,33 +105,35 @@ module Kitchen
95
105
  # @param locals [Array] the top-level list of directories and files to be transfered.
96
106
  # @param remote [String] the remote directory (kitchen_root).
97
107
  # @raise [ExpressFailed] if any of the threads raised an exception.
98
- def upload(locals, remote) # rubocop: disable Metrics/MethodLength
108
+ def upload(locals, remote)
99
109
  return super unless valid_remote_requirements?(remote)
100
110
 
101
- processed_locals = process_locals(locals)
102
- pool, exceptions = thread_pool(processed_locals)
103
- processed_locals.each do |local|
104
- pool.post do
105
- transfer(local, remote, session.options)
106
- rescue => e
107
- exceptions << e.cause
108
- end
109
- end
110
- pool.shutdown
111
- pool.wait_for_termination
112
-
113
- raise ExpressFailed, exceptions.pop unless exceptions.empty?
114
- end # rubocop: enable Metrics/MethodLength
111
+ start_time = Time.now
112
+ processed_local = process_locals(locals)
113
+ futures = create_futures(processed_local, remote)
114
+ all_done = Concurrent::Promise.zip(*futures).execute
115
+ all_done.value!
116
+ rescue => e
117
+ raise ExpressFailed, e.cause.to_s
118
+ ensure
119
+ Express.log(logger, "transport express complete", start_time)
120
+ end
115
121
 
116
122
  private
117
123
 
118
- # Creates the thread pool and exceptions queue.
124
+ # Creates the concurrent futures.
119
125
  #
120
- # @param processed_locals [Array] list of files and archives to be uploaded.
121
- # @return [Array(Concurrent::FixedThreadPool, Queue)]
126
+ # @param locals [Array] list of files and archives to be uploaded.
127
+ # @return [Array(Concurrent::Promise)]
122
128
  # @api private
123
- def thread_pool(processed_locals)
124
- [Concurrent::FixedThreadPool.new([processed_locals.length, 10].min), Queue.new]
129
+ def create_futures(locals, remote)
130
+ # Start upload futures
131
+ executor = Concurrent::FixedThreadPool.new([locals.length, 10].min)
132
+ locals.map do |local|
133
+ Concurrent::Promise.execute(executor: executor) do
134
+ transfer(local, remote, session.options)
135
+ end
136
+ end
125
137
  end
126
138
 
127
139
  # Ensures the remote host has the minimum-required executables to extract the archives.
@@ -134,8 +146,7 @@ module Kitchen
134
146
  execute("mkdir -p #{remote}")
135
147
  true
136
148
  rescue => e
137
- logger.debug("[#{Express::LOG_PREFIX}] Requirements not met on remote host for Express transport.")
138
- logger.debug("[#{Express::LOG_PREFIX}] #{e}")
149
+ Express.log(logger, "Requirements not met on remote host for Express transport.\n#{e}")
139
150
  false
140
151
  end
141
152
 
@@ -166,13 +177,11 @@ module Kitchen
166
177
  # @raise [StandardError] if the files could not be uploaded successfully.
167
178
  # @api private
168
179
  def transfer(local, remote, opts = {})
169
- logger.debug("[#{Express::LOG_PREFIX}] Transferring #{local} to #{remote}")
170
-
171
180
  Net::SSH.start(session.host, opts[:user], **opts) do |ssh|
172
- ssh.scp.upload!(local, remote, opts)
181
+ scp(ssh, local, remote, opts)
173
182
  extract(ssh, local, remote)
174
183
  rescue Net::SCP::Error => ex
175
- logger.debug("[#{Express::LOG_PREFIX}] upload failed with #{ex.message.strip}")
184
+ Express.log(logger, "upload failed with #{ex.message.strip}")
176
185
  raise "(#{ex.message.strip})"
177
186
  end
178
187
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-transport-express
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Steele
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-14 00:00:00.000000000 Z
11
+ date: 2025-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen
@@ -180,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  requirements: []
183
- rubygems_version: 3.3.7
183
+ rubygems_version: 3.3.27
184
184
  signing_key:
185
185
  specification_version: 4
186
186
  summary: Skip the long lines in Kitchen Transport!