assemblr 0.1.0 → 0.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: a56be997b4579f72b563f67e40f81a2aaaec974dc2568a345528e9171b0813c1
4
- data.tar.gz: 2d08815cb4b1c2d2245c75a9337a7586dfa2ef7e646ed726d8ed6527c90166c6
3
+ metadata.gz: 864016a190bb0067cd305a9824003a3cca4be4dd5eab9eb92b4f1e88ea388789
4
+ data.tar.gz: '08f75aa401dd8863a95897c07eacfffa5364b8cbefff9f39711167ff803867c9'
5
5
  SHA512:
6
- metadata.gz: 7571588a2718e3ed241d328b5d25d9fd9efb75054bee707c27d65b549e3dd944e909dc715d93958a0eeb18386cffead01d92306a800a3957d6b4dae5aeac9fa9
7
- data.tar.gz: cb10006737feca4ee02d42af22b6d06e9fc4fea7ae258d374f27e3363c37bcc03abf3877280cf29812d52ff9ae2fc2d4f5f5677752468dbca6d5f29b82f03a70
6
+ metadata.gz: 3d35cb5c921b0da7ffd6440aafe890e14c113dbee161be2351161c79c56f9ac10d228688c3bcbfe70181068ce159da0a7349032b6697b88d3eb57a71f1e3506f
7
+ data.tar.gz: 2b59abd44694032c93d6018b5698d9eb227fe5019c4aad8914805f32992bd7dfd60ab0015f4923e207b29f085bc44eafbc44c319057fa5286e8abe0260306ff5
data/.gitignore CHANGED
@@ -9,5 +9,7 @@
9
9
  /.idea/
10
10
  /examples/
11
11
 
12
+ *.gem
13
+
12
14
  /Gemfile.lock
13
15
  /.rakeTasks
data/README.md CHANGED
@@ -22,45 +22,33 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- This is an example of a simple assemblr script:
25
+ Assemblr is separated into modules. The core module contains simple logging
26
+ methods since they're needed by all other modules. It gets automatically
27
+ imported when requiring any sub-modules.
26
28
 
27
- ```ruby
28
- # frozen_string_literal: true
29
-
30
- require 'assemblr'
31
-
32
- # Configuration options. The values listed below are the defaults for assemblr.
33
- set :default_user, 'root' # set the default user nodes are assigned to
34
- set :default_group, 'default' # set the default group nodes are assigned to
35
- set :log, true # turn on or off built-in logging.
36
- set :quit_on_error, true # quit if anything goes wrong.
29
+ Here are the currently existing modules (although none of them are close to
30
+ finished):
37
31
 
38
- group :dev_servers do
39
- # Define nodes. All nodes defined in this block will be added to the
40
- # specified group (:dev_servers). After the block ends, the default user and
41
- # group will be restored for any other top-level node definitions.
42
- node '10.0.0.173'
43
- node '10.0.0.332'
44
- node '10.0.1.22'
45
- end
32
+ - `assemblr/shell`
33
+ - `assemblr/network`
34
+ - `assemblr/remote`
46
35
 
47
- node '10.0.0.33' # assigned to group :default and user 'root' as per the
48
- # defaults
36
+ There are currently two styles available to call the methods defined in these
37
+ modules:
49
38
 
50
- group :dev_servers do
51
- upload 'Gemfile', '/tmp/Gemmm' # upload a file to all nodes in a group
52
- upload 'lib', '/tmp',
53
- recursive: true # upload a directory to all nodes in a group
54
-
55
- remote_exec 'echo "Hello, world!"'
56
- end
39
+ ```ruby
40
+ require 'assemblr/shell'
57
41
 
58
- upload_to '10.0.2.43', 'root', 'message.txt', '/home/user/message.txt'
59
- result = remote_exec_on 'ruby', inject: ['puts "Hello, world!"', 'exit']
42
+ # Style 1
43
+ Shell.exec 'my_command'
60
44
 
61
- puts result
45
+ # Style 2
46
+ shell_exec 'my_command'
62
47
  ```
63
48
 
49
+ Both styles are automatically imported into the main scope when the file is
50
+ required.
51
+
64
52
  ## Development
65
53
 
66
54
  After checking out the repo, run `bin/setup` to install dependencies. You can
@@ -1,275 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'assemblr/version'
4
- require 'assemblr/logging'
5
- require 'assemblr/utilities'
3
+ $VERBOSE = nil
6
4
 
7
- require 'open3'
8
- require 'net/ssh'
9
- require 'net/scp'
5
+ require 'tty-logger'
10
6
 
7
+ ##
8
+ # Define the core Assemblr methods.
11
9
  module Assemblr
12
- $VERBOSE = nil # turn off warnings from external libraries
10
+ @@options = []
11
+ @@error_hook = -> {}
13
12
 
14
- # Local variables are prefixed with an 'm' for 'Module' as to not clash with
15
- # method names.
16
- m_nodes = []
17
- m_current_group = 'default'
18
- m_current_user = 'root'
13
+ def set_option(key, value)
14
+ return unless @@options.include?(key.to_sym)
19
15
 
20
- ##
21
- # Set a configuration option.
22
- #
23
- # Available options are:
24
- #
25
- # - *default_user* - the default user to assign nodes to
26
- # - *default_group* - the default group to assign nodes to
27
- # - *log* - turn on or off logging
28
- # - *quit_on_error* - quit if any error occur
29
- #
30
- # @return [void]
31
- define_method :set do |key, value|
32
- options = %i[log quit_on_error default_user default_group]
33
- unless options.include?(key)
34
- raise ArgumentError, "#{key} is not a valid option"
35
- end
36
-
37
- key = :current_user if key == :default_user
38
- key = :current_group if key == :default_group
39
-
40
- info %(config: set "#{key}" to "#{value}")
41
- eval "m_#{key} = value", binding, __FILE__, __LINE__
16
+ Assemblr.class_variable_set("@@#{key}", value)
42
17
  end
43
18
 
44
- ##
45
- # @!method local_exec(cmd, inject: [])
46
- # Execute a command locally.
47
- #
48
- # @param cmd [String] the command to run locally
49
- # @param inject [Array<String>] strings to inject into stdin
50
- #
51
- # @return [Array(String, Process::Status)]
52
- define_method :local_exec do |cmd, inject: []|
53
- log_string = "running `#{cmd}` locally"
54
- log_string += " with these inputs: #{inject}" unless inject.empty?
55
- info log_string
56
-
57
- result = ''
58
- status = nil
59
- Open3.popen2e(cmd) do |i, o, wait|
60
- inject.each do |line|
61
- line = line.strip
62
- i.write line + "\n"
63
- end
64
- i.close
65
- result = o.read
66
- status = wait.value
67
- end
68
-
69
- if status.exitstatus != 0
70
- code = status.exitstatus
71
- error "local command `#{cmd}` exited with a status of #{code}"
72
- else
73
- success "local command `#{cmd}` executed successfully"
74
- end
75
-
76
- return result, status
19
+ def get_option(key)
20
+ Assemblr.class_variable_get("@@#{key}")
77
21
  end
78
22
 
79
- ##
80
- # Execute a command on a remote machine using SSH. It returns the combined
81
- # stdout and stderr data.
82
- #
83
- # @param ip [String] the ip of the node to run the command on
84
- # @param user [String] the user to log in as
85
- # @param cmd [String] the command to execute remotely
86
- # @param inject [Array<String>] strings to inject into stdin
87
- #
88
- # @return [String]
89
- # @return [Array<String>]
90
- def remote_exec_on(ip, user, cmd, inject: [])
91
- info "attempting to execute `#{cmd}` on #{user}@#{ip}"
92
-
93
- result = ''
94
- Net::SSH.start(ip, user) do |ssh|
95
- ch = ssh.open_channel do |ch|
96
- ch.exec(cmd) do |ch, success|
97
- error "unable to execute `#{cmd}` on #{user}@#{ip}" unless success
98
-
99
- # Collect stdout data.
100
- ch.on_data do |_, data|
101
- result += data if data.is_a?(String)
102
- end
103
-
104
- # Collect stderr data.
105
- ch.on_extended_data do |_, _, data|
106
- result += data if data.is_a?(String)
107
- end
108
-
109
- inject.each do |line|
110
- ch.send_data(line + "\n")
111
- end
112
-
113
- ch.on_request 'exit-status' do |_, data|
114
- code = data.read_long
115
- if code.zero?
116
- success "`#{cmd}` executed successfully on #{user}@#{ip}"
117
- else
118
- error "`#{cmd}` returned status code #{code} on #{user}@#{ip}"
119
- end
120
- end
121
- end
122
- end
123
-
124
- ch.wait
125
- end
126
- result
23
+ def define_option(name, value)
24
+ Assemblr.class_variable_set("@@#{name}", value)
25
+ @@options << name.to_sym
127
26
  end
128
27
 
129
- ##
130
- # Upload a file to a node.
131
- #
132
- # @param ip [String] the node to upload to
133
- # @param user [String] the user to authenticate as
134
- # @param src [String] the file to upload
135
- # @param dest [String] the location to upload to
136
- # @param recursive [Bool] recursively upload files in a directory
137
- # @param timeout [Integer] how many seconds to wait before throwing an error
138
- #
139
- # @return [void]
140
- def upload_to(ip, user, src, dest, recursive: false, timeout: 5)
141
- info %(attempting to upload "#{src}" to #{user}@#{ip}:#{dest})
142
- Timeout.timeout timeout do
143
- Net::SCP.upload!(ip, user, src, dest, recursive: recursive)
144
- end
145
- rescue StandardError
146
- error %(failed to upload "#{src}" to #{user}@#{ip}:#{dest})
147
- rescue Timeout::Error
148
- error %(failed to upload "#{src}" to #{user}@#{ip}:#{dest} within #{timeout} seconds)
149
- else
150
- success %(uploaded "#{src}" to #{user}@#{ip}:#{dest})
28
+ def options
29
+ Assemblr.class_variable_get('@@options')
151
30
  end
152
31
 
153
- ##
154
- # @!method current_nodes()
155
- # Get all nodes in the current group.
156
- #
157
- # @return [void]
158
- define_method :current_nodes do
159
- return nodes if m_current_group == 'all'
160
-
161
- nodes.select { |n| n[:group] == m_current_group }
32
+ def error_hook
33
+ @@on_error
162
34
  end
163
35
 
164
- ##
165
- # Execute a command on all machines within the current group.
166
- #
167
- # @param cmd [String] the command to execute
168
- # @param inject [Array<String>] strings to inject into stdin
169
- #
170
- # @return [String] the combined output of the command
171
- def remote_exec(cmd, inject: [])
172
- results = []
173
- current_nodes.each do |n|
174
- result = remote_exec_on(n[:ip], n[:user], cmd, inject: inject)
175
- results << result
176
- end
177
- results
36
+ def on_error(&block)
37
+ @@on_error = block
178
38
  end
179
39
 
180
40
  ##
181
- # Upload a file or directory to all machines within the current group.
182
- #
183
- # @param src [String] the file to upload
184
- # @param dest [String] the location to upload the file to
185
- # @param recursive [Bool] recursively upload files in a directory
186
- # @param timeout [Integer] how many seconds to wait before throwing an error
187
- #
188
- # @return [void]
189
- def upload(src, dest, recursive: false, timeout: 5)
190
- current_nodes.each do |n|
191
- upload_to(n[:ip], n[:user], src, dest, recursive: recursive)
192
- end
41
+ # Generates method definitions in all supported styles.
42
+ def expose_method(name)
43
+ prefix = to_s.downcase.gsub('::', '_').delete_prefix('assemblr_')
44
+ eval "#{self}.define_method(:#{prefix}_#{name}, &#{self}.method(:#{name}))"
45
+ eval "#{self}.define_singleton_method(:#{name}, &#{self}.method(:#{name}))"
193
46
  end
47
+ end
194
48
 
195
- ##
196
- # @!method group(group, &block)
197
- # Temporarily change current_group to refer to the specified group while
198
- # within the passed block.
199
- #
200
- # @param group [Symbol] the group to temporarily switch to
201
- #
202
- # @return [void]
203
- define_method :group do |group, &block|
204
- info %(entered group block with group "#{group}")
205
-
206
- # Temporarily switch to the target group.
207
- old_current_group = m_current_group
208
- m_current_group = group.to_s
209
-
210
- block.call
211
-
212
- m_current_group = old_current_group
213
-
214
- info %(exited group block with group "#{group}")
215
- end
49
+ include Assemblr
216
50
 
51
+ module Assemblr
217
52
  ##
218
- # @!method current_group
219
- # Get the current default group.
220
- #
221
- # @return [String]
53
+ # Define some quick-and-easy logging methods.
54
+ module Log
55
+ define_option :log, true
56
+ define_option :log_error_hook, true
222
57
 
223
- ##
224
- # @!method current_user
225
- # Get the current default user.
226
- #
227
- # @return [String]
228
- %w[group user].each do |item|
229
- define_method "current_#{item}" do
230
- eval "m_current_#{item}"
58
+ TTYLogger = TTY::Logger.new do |config|
59
+ config.metadata = %i[time date]
231
60
  end
232
- end
233
61
 
234
- ##
235
- # @!method node(ip, group: current_group, user: current_user)
236
- # Add a node to the list of nodes.
237
- #
238
- # @param ip [String] the ip address of the node
239
- # @param group [Symbol] the group to assign the node to
240
- # @param user [String] the user to associate with the node
241
- #
242
- # @return [Hash{ip=>String, group=>String, user=>String}] the added node
243
- define_method :node do |ip, group: m_current_group, user: m_current_user|
244
- group = group.to_s
245
- if group == 'all'
246
- error = ArgumentError.new('nodes can not be assigned to the "all" group')
247
- raise error
62
+ def self.included(_)
63
+ expose_method :info
64
+ expose_method :success
65
+ expose_method :warn
66
+ expose_method :error
248
67
  end
249
68
 
250
- l_node = { ip: ip, group: group, user: user }
251
- m_nodes << l_node
252
- info "added node: #{l_node}"
253
-
254
- l_node
255
- end
256
-
257
- ##
258
- # @!method nodes()
259
- # Get all registered nodes.
260
- #
261
- # @return [Array<Hash{ip=>String, group=>String, user=>String}>]
262
- define_method :nodes do
263
- m_nodes
264
- end
69
+ class << self
70
+ ##
71
+ # @!method info(message)
72
+ # Logs an info message.
73
+ #
74
+ # @return [void]
75
+
76
+ ##
77
+ # @!method success(message)
78
+ # Logs a success message.
79
+ #
80
+ # @return [void]
81
+
82
+ ##
83
+ # @!method warn(message)
84
+ # Logs a warning message.
85
+ #
86
+ # @return [void]
87
+ %i[info success warn].each do |level|
88
+ define_method level do |*args, &block|
89
+ TTYLogger.send(level, *args, &block) if get_option(:log)
90
+ end
91
+ end
265
92
 
266
- define_method :nodes_reachable? do
267
- reachable = true
268
- m_nodes.each do |node|
269
- reachable &&= reachable?(node[:ip])
93
+ ##
94
+ # @!method error(message)
95
+ # Logs a error message. If configured, this will call the on_error proc
96
+ #
97
+ # @return [void]
98
+ define_method :error do |*args, &block|
99
+ TTYLogger.send(:error, *args, &block) if get_option(:log)
100
+ error_hook.call if get_option(:log_error_hook)
101
+ end
270
102
  end
271
- reachable
272
103
  end
273
104
  end
274
105
 
275
- extend Assemblr
106
+ include Assemblr::Log
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ $VERBOSE = nil
4
+
5
+ require 'assemblr'
6
+
7
+ require 'socket'
8
+ require 'net/ping'
9
+
10
+ module Assemblr
11
+ ##
12
+ # Defines methods for common network functions.
13
+ module Network
14
+ def self.included(_)
15
+ expose_method :local_ip
16
+ expose_method :ip_reachable?
17
+ end
18
+
19
+ class << self
20
+ ##
21
+ # Retrieve the first ip address that is not '127.0.0.1' on the local
22
+ # machine.
23
+ #
24
+ # @param pattern [Regexp] a pattern to test the IPs against
25
+ #
26
+ # @return [String]
27
+ def local_ip(pattern = //)
28
+ Socket.ip_address_list.each do |ip|
29
+ address = ip.ip_address
30
+ next if address == '127.0.0.1'
31
+ return address if address.match?(pattern)
32
+ end
33
+ ''
34
+ end
35
+
36
+ ##
37
+ # Check if a remote ip can be contacted.
38
+ #
39
+ # @return [Boolean]
40
+ def ip_reachable?(ip)
41
+ external = Net::Ping::External.new(ip)
42
+
43
+ log_info %(attempting to contact host "#{ip}")
44
+ reachable = external.ping || external.ping6
45
+ if reachable
46
+ log_success %(host "#{ip}" is reachable)
47
+ else
48
+ log_error %(unable to contact host "#{ip}")
49
+ end
50
+
51
+ reachable
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ include Assemblr::Network
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'assemblr'
4
+
5
+ require 'timeout'
6
+ require 'net/ssh'
7
+ require 'net/scp'
8
+
9
+ module Assemblr
10
+ ##
11
+ # Defines methods to handle interacting with remote nodes.
12
+ module Remote
13
+ @@nodes = []
14
+
15
+ define_option :remote_default_group, :default
16
+ define_option :remote_default_user, 'root'
17
+
18
+ def self.included(_)
19
+ expose_method :group
20
+ expose_method :upload
21
+ expose_method :exec
22
+ expose_method :current_nodes
23
+ expose_method :node
24
+ expose_method :nodes
25
+ end
26
+
27
+ class << self
28
+ ##
29
+ # Defines methods availabile only while in a group block.
30
+ module Group
31
+ def self.remote_upload(src, dest, recursive: false, timeout: 5)
32
+ Remote.current_nodes.each do |n|
33
+ Remote.upload(n[:ip], n[:user], src, dest,
34
+ recursive: recursive,
35
+ timeout: timeout)
36
+ end
37
+ end
38
+
39
+ def self.remote_exec(cmd, inject: [])
40
+ results = []
41
+ Remote.current_nodes.each do |n|
42
+ result = Remote.remote_exec(n[:ip], n[:user], cmd, inject: inject)
43
+ results << result
44
+ end
45
+ results
46
+ end
47
+ end
48
+
49
+ # Execute a command on a remote machine using SSH. It returns the
50
+ # combined stdout and stderr data.
51
+ #
52
+ # @param ip [String] the ip of the node to run the command on
53
+ # @param user [String] the user to log in as
54
+ # @param cmd [String] the command to execute remotely
55
+ # @param inject [Array<String>] strings to inject into stdin
56
+ #
57
+ # @return [String]
58
+ def exec(ip, user, cmd, inject: [])
59
+ log_info "attempting to execute `#{cmd}` on #{user}@#{ip}"
60
+
61
+ result = ''
62
+ exit_code = 0
63
+ Net::SSH.start(ip, user) do |ssh|
64
+ ch = ssh.open_channel do |ch|
65
+ ch.exec(cmd) do |ch, success|
66
+ log_error "unable to execute `#{cmd}` on #{user}@#{ip}" unless success
67
+
68
+ # Collect stdout data.
69
+ ch.on_data do |_, data|
70
+ result += data if data.is_a?(String)
71
+ end
72
+
73
+ # Collect stderr data.
74
+ ch.on_extended_data do |_, _, data|
75
+ result += data if data.is_a?(String)
76
+ end
77
+
78
+ inject.each do |line|
79
+ ch.send_data(line + "\n")
80
+ end
81
+
82
+ ch.on_request 'exit-status' do |_, data|
83
+ exit_code = data.read_long
84
+ if exit_code.zero?
85
+ log_success "`#{cmd}` executed successfully on #{user}@#{ip}"
86
+ else
87
+ log_error "`#{cmd}` returned status code #{exit_code} on #{user}@#{ip}"
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ ch.wait
94
+ end
95
+ [result, exit_code]
96
+ end
97
+
98
+ ##
99
+ # Return all nodes within the current default group.
100
+ #
101
+ # @return [Array<Hash{ip=>String, group=>String, user=>String}>]
102
+ def current_nodes
103
+ return nodes if get_option(:remote_default_group) == 'all'
104
+
105
+ @@nodes.select { |n| n[:group] == get_option(:remote_default_group).to_s }
106
+ end
107
+
108
+ ##
109
+ # Temporarily change the default group to refer to the specified group
110
+ # while within the passed block.
111
+ #
112
+ # This method also changes the behavior of `remote_exec` and
113
+ # `remote_upload` while within the given block. Both functions don't take
114
+ # an IP # and a user, as they collect the information for all nodes
115
+ # within the group. In that case, the return value is an array of the
116
+ # results given by each node.
117
+ #
118
+ # @param group [Symbol] the group to temporarily switch to
119
+ #
120
+ # @return [void]
121
+ def group(group, &block)
122
+ log_info %(entered group block with group "#{group}")
123
+
124
+ # Temporarily switch to the target group.
125
+ old_default_group = get_option(:remote_default_group)
126
+ set_option(:remote_default_group, group.to_s)
127
+
128
+ Group.class_eval(&block)
129
+
130
+ set_option(:remote_default_group, old_default_group)
131
+
132
+ log_info %(exited group block with group "#{group}")
133
+ end
134
+
135
+ ##
136
+ # Upload a file or directory to a node.
137
+ #
138
+ # @param ip [String] the node to upload to
139
+ # @param user [String] the user to authenticate as
140
+ # @param src [String] the file to upload
141
+ # @param dest [String] the location to upload to
142
+ # @param recursive [Bool] recursively upload files in a directory
143
+ # @param timeout [Integer] how many seconds to wait before throwing an error
144
+ #
145
+ # @return [void]
146
+ def upload(ip, user, src, dest, recursive: false, timeout: 5)
147
+ log_info %(attempting to upload "#{src}" to #{user}@#{ip}:#{dest})
148
+ Timeout.timeout timeout do
149
+ Net::SCP.upload!(ip, user, src, dest, recursive: recursive)
150
+ end
151
+ rescue Timeout::Error
152
+ log_error %(failed to upload "#{src}" to #{user}@#{ip}:#{dest} within #{timeout} seconds)
153
+ rescue StandardError
154
+ log_error %(failed to upload "#{src}" to #{user}@#{ip}:#{dest})
155
+ else
156
+ log_success %(uploaded "#{src}" to #{user}@#{ip}:#{dest})
157
+ end
158
+
159
+ ##
160
+ # Add a node to the list of nodes.
161
+ #
162
+ # @param ip [String] the ip address of the node
163
+ # @param group [Symbol] the group to assign the node to
164
+ # @param user [String] the user to associate with the node
165
+ #
166
+ # @return [Hash{ip=>String, group=>String, user=>String}] the added node
167
+ def node(ip,
168
+ group: get_option(:remote_default_group),
169
+ user: get_option(:remote_default_user))
170
+ group = group.to_s
171
+ if group == 'all'
172
+ message = 'nodes can not be assigned to the "all" group'
173
+ error = ArgumentError.new(message)
174
+ raise error
175
+ end
176
+
177
+ l_node = { ip: ip, group: group, user: user }
178
+ @@nodes << l_node
179
+ log_info "added node: #{l_node}"
180
+
181
+ l_node
182
+ end
183
+
184
+ ##
185
+ # Get all registered nodes.
186
+ #
187
+ # @return [Array<Hash{ip=>String, group=>String, user=>String}>]<Paste>
188
+ def nodes
189
+ @@nodes
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ include Assemblr::Remote
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'assemblr'
4
+
5
+ require 'open3'
6
+
7
+ module Assemblr
8
+ ##
9
+ # Defines methods for common shell commands.
10
+ module Shell
11
+ def self.included(_)
12
+ expose_method :exec
13
+ end
14
+
15
+ class << self
16
+ # Execute a command locally.
17
+ #
18
+ # @param cmd [String] the command to run locally
19
+ # @param inject [Array<String>] strings to inject into stdin
20
+ #
21
+ # @return [Array(String, Process::Status)]
22
+ def exec(cmd, inject: [])
23
+ log_string = "running `#{cmd}` locally"
24
+ log_string += " with these inputs: #{inject}" unless inject.empty?
25
+ log_info log_string
26
+
27
+ result = ''
28
+ status = nil
29
+ Open3.popen2e(cmd) do |i, o, wait|
30
+ inject.each do |line|
31
+ line = line.strip
32
+ i.write line + "\n"
33
+ end
34
+ i.close
35
+ result = o.read
36
+ status = wait.value
37
+ end
38
+
39
+ if status.exitstatus != 0
40
+ code = status.exitstatus
41
+ log_error "local command `#{cmd}` exited with a status of #{code}"
42
+ else
43
+ log_success "local command `#{cmd}` executed successfully"
44
+ end
45
+
46
+ [result, status]
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ include Assemblr::Shell
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Assemblr
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: assemblr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Burmeister-Morrison
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-08 00:00:00.000000000 Z
11
+ date: 2020-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -110,8 +110,9 @@ files:
110
110
  - bin/console
111
111
  - bin/setup
112
112
  - lib/assemblr.rb
113
- - lib/assemblr/logging.rb
114
- - lib/assemblr/utilities.rb
113
+ - lib/assemblr/network.rb
114
+ - lib/assemblr/remote.rb
115
+ - lib/assemblr/shell.rb
115
116
  - lib/assemblr/version.rb
116
117
  homepage: https://github.com/rburmorrison/assemblr
117
118
  licenses:
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'tty-logger'
4
-
5
- module Assemblr
6
- Logger = TTY::Logger.new do |config|
7
- config.metadata = %i[time date]
8
- end
9
-
10
- m_log = true
11
- m_quit_on_error = true
12
-
13
- ##
14
- # @!method info(message)
15
- # Logs an info message.
16
- #
17
- # @return [void]
18
-
19
- ##
20
- # @!method success(message)
21
- # Logs a success message.
22
- #
23
- # @return [void]
24
-
25
- ##
26
- # @!method warn(message)
27
- # Logs a warning message.
28
- #
29
- # @return [void]
30
- %i[info success warn].each do |level|
31
- define_method level do |*args, &block|
32
- return unless m_log
33
-
34
- Logger.send(level, *args, &block)
35
- end
36
- end
37
-
38
- ##
39
- # @!method error(message)
40
- # Logs a error message. If configured, this will end the task with error code
41
- # 1.
42
- #
43
- # @return [void]
44
- define_method :error do |*args, &block|
45
- Logger.send(:error, *args, &block) if m_log
46
- exit 1 if m_quit_on_error
47
- end
48
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'socket'
4
- require 'net/ping'
5
-
6
- ##
7
- # A collection of utility functions for assemblr.
8
- module Assemblr
9
- ##
10
- # Retrieve the first ip address that is not '127.0.0.1' on the local machine.
11
- #
12
- # @param pattern [Regexp] a pattern to test the ips against
13
- def get_local_ip(pattern = //)
14
- Socket.ip_address_list.each do |ip|
15
- address = ip.ip_address
16
- next if address == '127.0.0.1'
17
- return address if address.match?(pattern)
18
- end
19
- ''
20
- end
21
-
22
- ##
23
- # Check if a remote machine can be contacted.
24
- #
25
- # @return [Bool]
26
- def reachable?(ip)
27
- external = Net::Ping::External.new(ip)
28
-
29
- info %(attempting to contact host "#{ip}"...)
30
- reachable = external.ping || external.ping6
31
- if reachable
32
- success %(host "#{ip}" is reachable)
33
- else
34
- error %(unable to contact host "#{ip}")
35
- end
36
-
37
- reachable
38
- end
39
- end