assemblr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a56be997b4579f72b563f67e40f81a2aaaec974dc2568a345528e9171b0813c1
4
+ data.tar.gz: 2d08815cb4b1c2d2245c75a9337a7586dfa2ef7e646ed726d8ed6527c90166c6
5
+ SHA512:
6
+ metadata.gz: 7571588a2718e3ed241d328b5d25d9fd9efb75054bee707c27d65b549e3dd944e909dc715d93958a0eeb18386cffead01d92306a800a3957d6b4dae5aeac9fa9
7
+ data.tar.gz: cb10006737feca4ee02d42af22b6d06e9fc4fea7ae258d374f27e3363c37bcc03abf3877280cf29812d52ff9ae2fc2d4f5f5677752468dbca6d5f29b82f03a70
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.idea/
10
+ /examples/
11
+
12
+ /Gemfile.lock
13
+ /.rakeTasks
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in assemblr.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Ryan Burmeister-Morrison
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,84 @@
1
+ # Assemblr
2
+
3
+ A small DSL for the construction of quick automation tasks.
4
+
5
+ **NOTE:** This version is just a prototype. It will be completely re-written.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'assemblr'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install assemblr
22
+
23
+ ## Usage
24
+
25
+ This is an example of a simple assemblr script:
26
+
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.
37
+
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
46
+
47
+ node '10.0.0.33' # assigned to group :default and user 'root' as per the
48
+ # defaults
49
+
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
57
+
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']
60
+
61
+ puts result
62
+ ```
63
+
64
+ ## Development
65
+
66
+ After checking out the repo, run `bin/setup` to install dependencies. You can
67
+ also run `bin/console` for an interactive prompt that will allow you to
68
+ experiment.
69
+
70
+ To install this gem onto your local machine, run `bundle exec rake install`. To
71
+ release a new version, update the version number in `version.rb`, and then run
72
+ `bundle exec rake release`, which will create a git tag for the version, push
73
+ git commits and tags, and push the `.gem` file to
74
+ [rubygems.org](https://rubygems.org).
75
+
76
+ ## Contributing
77
+
78
+ Bug reports and pull requests are welcome on GitHub at
79
+ https://github.com/rburmorrison/assemblr.
80
+
81
+ ## License
82
+
83
+ The gem is available as open source under the terms of the [MIT
84
+ License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ task default: :spec
5
+
6
+ task 'rubocop' do
7
+ sh 'rubocop *'
8
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/assemblr/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'assemblr'
7
+ spec.version = Assemblr::VERSION
8
+ spec.authors = ['Ryan Burmeister-Morrison']
9
+ spec.email = ['rburmeistermorrison@gmail.com']
10
+
11
+ spec.summary = 'A small DSL for the construction of quick automation tasks.'
12
+ spec.homepage = 'https://github.com/rburmorrison/assemblr'
13
+ spec.license = 'MIT'
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/rburmorrison/assemblr'
18
+
19
+ # Specify which files should be added to the gem when it is released. The `git
20
+ # ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_development_dependency 'rake', '~> 12.0'
31
+ spec.add_development_dependency 'rubocop', '~> 0.80'
32
+
33
+ spec.add_runtime_dependency 'net-ping', '~> 2.0'
34
+ spec.add_runtime_dependency 'net-scp', '~> 2.0'
35
+ spec.add_runtime_dependency 'net-ssh', '~> 5.2'
36
+ spec.add_runtime_dependency 'tty-logger', '~> 0.3'
37
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'assemblr'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'assemblr/version'
4
+ require 'assemblr/logging'
5
+ require 'assemblr/utilities'
6
+
7
+ require 'open3'
8
+ require 'net/ssh'
9
+ require 'net/scp'
10
+
11
+ module Assemblr
12
+ $VERBOSE = nil # turn off warnings from external libraries
13
+
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'
19
+
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__
42
+ end
43
+
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
77
+ end
78
+
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
127
+ end
128
+
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})
151
+ end
152
+
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 }
162
+ end
163
+
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
178
+ end
179
+
180
+ ##
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
193
+ end
194
+
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
216
+
217
+ ##
218
+ # @!method current_group
219
+ # Get the current default group.
220
+ #
221
+ # @return [String]
222
+
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}"
231
+ end
232
+ end
233
+
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
248
+ end
249
+
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
265
+
266
+ define_method :nodes_reachable? do
267
+ reachable = true
268
+ m_nodes.each do |node|
269
+ reachable &&= reachable?(node[:ip])
270
+ end
271
+ reachable
272
+ end
273
+ end
274
+
275
+ extend Assemblr
@@ -0,0 +1,48 @@
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
@@ -0,0 +1,39 @@
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Assemblr
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: assemblr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Burmeister-Morrison
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.80'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.80'
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-ping
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: net-scp
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: net-ssh
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-logger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.3'
97
+ description:
98
+ email:
99
+ - rburmeistermorrison@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - assemblr.gemspec
110
+ - bin/console
111
+ - bin/setup
112
+ - lib/assemblr.rb
113
+ - lib/assemblr/logging.rb
114
+ - lib/assemblr/utilities.rb
115
+ - lib/assemblr/version.rb
116
+ homepage: https://github.com/rburmorrison/assemblr
117
+ licenses:
118
+ - MIT
119
+ metadata:
120
+ homepage_uri: https://github.com/rburmorrison/assemblr
121
+ source_code_uri: https://github.com/rburmorrison/assemblr
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 2.3.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.1.2
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: A small DSL for the construction of quick automation tasks.
141
+ test_files: []