web47command 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b814dd9f0d828dafba8d68fbc7afdd1b201a1d9ecb6dac8b844d93ae07025b3a
4
+ data.tar.gz: 21c18f8d721ae3a2e65f60d9dd39a67e61e2e64b41de447a77a2eb7e15d3cf35
5
+ SHA512:
6
+ metadata.gz: 7807c3c193bcb942db4c70da4c0474635813d4c67cba2ebac356a03529f01e963f1d5a4b083e9edbdc25e97107c8765d44072af917c7d9966acbbac8854213c6
7
+ data.tar.gz: cebde8c5a6d33d0d514ed31fe7682296d1d922ac619f717ee0c69b7c258193887e31a32f298d4a07c5ee0069774b4a0afb49f9c16d87588233adb62d05ace6e8
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 App47
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # web47command
2
+ Command jobs are types of jobs that use the command line for execution. This library contains a common set of actions that are performed by these types of jobs
3
+
4
+ ## We don't need no sticking badges
5
+
6
+ *
7
+ *
8
+ * Develop:
9
+ * Master:
10
+
11
+ ## Requirements
12
+
13
+ * Ruby 2.4.1
14
+
15
+ ### Working with Bundler and RVM
16
+
17
+ This project manages Ruby versions via [RVM](http://rvm.beginrescueend.com/) and manages dependencies via [Bundler](http://gembundler.com/).
18
+
19
+ You must first [install RVM](http://rvm.beginrescueend.com/rvm/install/).
20
+ Then install Ruby 2.4.1 ([version 2.4.1-p111](http://rvm.beginrescueend.com/interpreters/ruby/)) via RVM.
21
+ ``` shell script
22
+ rvm install 2.4.1
23
+ ```
24
+ You'll now notice that this project (as well as other App47 ones) contains a .rvmc file, which is executed upon opening the project's root directory in a terminal (and IDE's like RubyMine). The .rvmc file simply states `rvm ruby-2.4.1` which tells RVM to ensure the Ruby version to use for this project is 2.4.1.
25
+
26
+ Please note, your Ruby `2.4.1` version might need bundler installed:
27
+ ``` shell script
28
+ gem install bundler -v 1.17.3
29
+ ```
30
+
31
+ To set up this project's dependencies, which are defined in the file, `Gemfile`, you should first run
32
+ ``` shell script
33
+ bundle install --path vendor
34
+ ```
35
+ The `--path vendor` simply puts all gem files in a `vendor` directory.
36
+
37
+ ## Development
38
+
39
+ Your `RubyMine` environment should be setup now, however to verify all is well, please run the test suite
40
+ ``` shell script
41
+ bundle exec rake test
42
+ ```
43
+
44
+ ## Deployment
45
+ The `web47command` project is a gem that will be deployed via [Ruby Gems](https://rubygems.org). When an update is ready, the following steps should be followed
46
+
47
+ 1. Build the gem `gem build web47command.gemspec`
48
+ 2. Push the new gem to [Ruby Gems](https://rubygems.org) `gem push web47command-version.gem`
49
+ 3. There may be a delay when using the gem file
50
+
51
+ ## Usage
52
+ ### Importing the gem
53
+ To use the `app47command` gem in a project, first add the gem to your Gemfile in one of two ways
54
+
55
+ Using the gem from [Ruby Gems](https://rubygems.org)
56
+ ```rbenv-gemsets
57
+ gem 'web47command'
58
+ ```
59
+
60
+ If you need the gem immediately or need to pull from development branch, you can use the git repo
61
+ ```rbenv-gemsets
62
+ gem 'web47command', git: 'git@github.com:App47/web47command.git', branch: :master
63
+ ```
64
+ or from the develop branch
65
+ ```rbenv-gemsets
66
+ gem 'web47command', git: 'git@github.com:App47/web47command.git', branch: :develop
67
+ ```
68
+
69
+ _Please do not ship to production code using the git repo, as the production servers will not have keys to pull from the web47command repo_
70
+
71
+ ### Remove the following files
72
+ 1. `Job`
73
+ 2. `JobLog`
74
+ 3. `CommandLogLog`
75
+ 4. `TrimJobs`
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cron
4
+ #
5
+ # Clean up Jobs
6
+ #
7
+ class TrimJobs < TrimCollection
8
+ #
9
+ # Fetch each Audit Log and delete it if hasn't been updated in 90 days
10
+ #
11
+ def collection
12
+ CommandJob.all
13
+ end
14
+
15
+ #
16
+ # Check which audit logs we wanted deleted
17
+ #
18
+ # Should be older than 90 days and either not a user model audit log or the model associated with
19
+ # the UserModelAuditLog has been deleted
20
+ #
21
+ def allowed_time_for_item(job)
22
+ job.ttl.days.ago.utc
23
+ rescue StandardError => error
24
+ App47Logger.log_warn "Unable to determine if job should be archived: #{job.inspect}", error
25
+ 30.days.ago.utc
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,269 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Base class for all jobs that will be run on builds
5
+ #
6
+ class CommandJob
7
+ include StandardModel
8
+ #
9
+ # Constants
10
+ #
11
+ STATE_NEW = 'new' unless defined? STATE_NEW
12
+ STATE_WIP = 'working' unless defined? STATE_WIP
13
+ STATE_RETRYING = 'retrying' unless defined? STATE_RETRYING
14
+ STATE_SUCCESS = 'success' unless defined? STATE_SUCCESS
15
+ STATE_FAIL = 'failure' unless defined? STATE_FAIL
16
+ ALL_STATES = [STATE_NEW, STATE_WIP, STATE_RETRYING, STATE_SUCCESS, STATE_FAIL].freeze unless defined? ALL_STATES
17
+ #
18
+ # Fields
19
+ #
20
+ field :state, type: String, default: STATE_NEW
21
+ field :retries, type: Integer, default: 0
22
+ field :max_retries, type: Integer, default: 5
23
+ field :result, type: String
24
+ field :error_message, type: String
25
+ #
26
+ # Relationships
27
+ #
28
+ has_many :logs, class_name: 'JobLog', dependent: :destroy
29
+ #
30
+ # Validations
31
+ #
32
+ validates :state, inclusion: { in: ALL_STATES }
33
+
34
+ #
35
+ # Default time to keep a job before auto archiving it
36
+ #
37
+ def ttl
38
+ 30
39
+ end
40
+
41
+ #
42
+ # Return the name of this job
43
+ #
44
+ def name
45
+ self.class.to_s.underscore.humanize
46
+ end
47
+
48
+ #
49
+ # True if in new status
50
+ #
51
+ def new?
52
+ STATE_NEW.eql?(state)
53
+ end
54
+
55
+ #
56
+ # True if in WIP status
57
+ #
58
+ def wip?
59
+ [STATE_RETRYING, STATE_WIP].include?(state)
60
+ end
61
+
62
+ #
63
+ # True if in success status
64
+ #
65
+ def success?
66
+ STATE_SUCCESS.eql?(state)
67
+ end
68
+
69
+ #
70
+ # True if in fail status
71
+ #
72
+ def fail?
73
+ STATE_FAIL.eql?(state)
74
+ end
75
+
76
+ #
77
+ # If we is finished, failed or success
78
+ #
79
+ def finished?
80
+ [STATE_FAIL, STATE_SUCCESS].include?(state)
81
+ end
82
+
83
+ #
84
+ # If we are still running
85
+ #
86
+ def running?
87
+ !finished?
88
+ end
89
+
90
+ #
91
+ # Return the job's status and information in a hash that could be used to return to a calling
92
+ # api
93
+ #
94
+ def status
95
+ status = { state: state }
96
+ status[:message] = error_message if error_message.present?
97
+ status
98
+ end
99
+
100
+ def perform
101
+ if fail?
102
+ self.retries = 0
103
+ self.error_message = 'Retrying'
104
+ end
105
+
106
+ begin
107
+ save!
108
+ run
109
+ rescue StandardError => error
110
+ log_error 'Unable to start job', error
111
+ set state: STATE_FAIL, error_message: error.message
112
+ end
113
+ end
114
+
115
+ def run
116
+ set state: STATE_WIP
117
+ # Run the job
118
+ run!
119
+ set error_message: '', state: STATE_SUCCESS
120
+ rescue StandardError => error
121
+ if retries >= max_retries
122
+ log_error "Unable to run job id: #{id}, done retrying", error
123
+ set state: STATE_FAIL, error_message: "Failed final attempt: #{error.message}"
124
+ else
125
+ log_error "Unable to run job id: #{id}, retrying!!", error
126
+ add_log "Unable to run job: #{error.message}, retrying!!"
127
+ set error_message: "Failed attempt # #{retries}: #{error.message}", retries: retries + 1
128
+ delay(run_at: 10.seconds.from_now).run
129
+ end
130
+ end
131
+
132
+ handle_asynchronously :run if Delayed::Worker.delay_jobs
133
+
134
+ #
135
+ # Determine the correct action to take and get it started
136
+ #
137
+ def run!
138
+ raise 'Incomplete class, concrete implementation should implement #run!'
139
+ end
140
+
141
+ #
142
+ # Write out the contents to the file
143
+ #
144
+ def write_file(path, contents)
145
+ File.open(path, 'w') { |f| f.write(contents) }
146
+ add_log "Saving:\n #{contents}\nto: #{path}"
147
+ end
148
+
149
+ #
150
+ # Download a file to the given path
151
+ #
152
+ def download_file(file_url, file_path)
153
+ download = URI.parse(file_url).open
154
+ IO.copy_stream(download, file_path)
155
+ add_log "Downloaded file: #{file_url} to #{file_path}"
156
+ end
157
+
158
+ #
159
+ # Copy a given file to a new location and record the log
160
+ #
161
+ def copy_file(from_path, to_path)
162
+ if File.exist? from_path
163
+ FileUtils.cp(from_path, to_path)
164
+ add_log "Copy file from: #{from_path} to: #{to_path}"
165
+ else
166
+ add_log "File not found: #{from_path}, copy not performed"
167
+ end
168
+ end
169
+
170
+ #
171
+ # Copy a given directory to a new location and record the log
172
+ #
173
+ def copy_dir(dir, to_path)
174
+ FileUtils.cp_r dir, to_path
175
+ add_log "Copy directory from: #{dir} to: #{to_path}"
176
+ end
177
+
178
+ #
179
+ # Remove the given file name
180
+ #
181
+ def remove_file(file_path)
182
+ return unless File.exist?(file_path)
183
+
184
+ FileUtils.remove_file file_path
185
+ add_log "Removing file: #{file_path}"
186
+ end
187
+
188
+ #
189
+ # Remove the given file name
190
+ #
191
+ def remove_dir(dir_path)
192
+ return unless File.exist?(dir_path)
193
+
194
+ FileUtils.remove_dir dir_path
195
+ add_log "Removing dir: #{dir_path}"
196
+ end
197
+
198
+ #
199
+ # Create a directory and record it
200
+ #
201
+ def mkdir(dir)
202
+ return if File.exist?(dir)
203
+
204
+ FileUtils.mkdir dir
205
+ add_log "Created directory: #{dir}"
206
+ end
207
+
208
+ alias make_dir mkdir
209
+
210
+ #
211
+ # Unzip a given file
212
+ #
213
+ def unzip_file(file_path, to_dir)
214
+ run_command "unzip #{file_path}", to_dir, error_texts: 'unzip:'
215
+ end
216
+
217
+ #
218
+ # Run the command capturing the command output and any standard error to the log.
219
+ #
220
+ def run_command(command, dir = '/tmp', options = {})
221
+ command = command.join(' ') if command.is_a?(Array)
222
+ output = Tempfile.open('run-command-', '/tmp') do |f|
223
+ Dir.chdir(dir) { `#{command} > #{f.path} 2>&1` }
224
+ mask_keywords(f.open.read, options[:mask_texts])
225
+ end
226
+ output = 'Success' if output.blank?
227
+ command = mask_keywords(command, options[:mask_texts])
228
+ CommandJobLog.create!(job: self, dir: dir, command: command, message: output)
229
+ check_for_text(output, options[:error_texts], true)
230
+ check_for_text(output, options[:required_texts], false)
231
+ output
232
+ end
233
+
234
+ #
235
+ # Mask keywords if given in the command
236
+ #
237
+ def mask_keywords(output, keywords = [])
238
+ return output if keywords.blank?
239
+
240
+ keywords = [keywords] if keywords.is_a?(String)
241
+ keywords.each do |keyword|
242
+ output = output.gsub(keyword, '***********')
243
+ end
244
+ output
245
+ end
246
+
247
+ #
248
+ # Check if any occurrences were found (or not found)
249
+ #
250
+ def check_for_text(output, texts = [], inclusive = true)
251
+ return if texts.blank?
252
+
253
+ texts = [texts] if texts.is_a?(String)
254
+ texts.each do |text|
255
+ if inclusive
256
+ raise "Error: found text (#{text}) - #{output}" if output.match?(/#{text}/)
257
+ else
258
+ raise "Error: missing text (#{text}) - #{output}" unless output.match?(/#{text}/)
259
+ end
260
+ end
261
+ end
262
+
263
+ #
264
+ # Add a job log message
265
+ #
266
+ def add_log(message)
267
+ JobLog.create!(job: self, message: message)
268
+ end
269
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Capture command log messages for a given job
5
+ #
6
+ class CommandJobLog < JobLog
7
+ #
8
+ # Fields
9
+ #
10
+ field :command, type: String
11
+ field :dir, type: String
12
+ #
13
+ # Validations
14
+ #
15
+ validates :command, presence: true
16
+
17
+ #
18
+ # Display message
19
+ #
20
+ def display_message
21
+ "Dir: #{dir}\nCommand: #{command}\nOutput: #{message}"
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Capture log messages for a given job
5
+ #
6
+ class JobLog
7
+ include StandardModel
8
+ #
9
+ # Fields
10
+ #
11
+ field :message, type: String
12
+ #
13
+ # Relationships
14
+ #
15
+ belongs_to :job, inverse_of: :logs, class_name: 'CommandJob'
16
+ #
17
+ # Validations
18
+ #
19
+ validates :message, presence: true
20
+
21
+ #
22
+ # Display message
23
+ #
24
+ def display_message
25
+ message
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ require 'web47command/version'
2
+ require 'app/models/job_log'
3
+ require 'app/models/command_job_log'
4
+ require 'app/models/command_job'
5
+ #
6
+ # Cron
7
+ #
8
+ require 'app/jobs/cron/trim_jobs'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Web47command
4
+ VERSION = '0.0.1'
5
+ end
metadata ADDED
@@ -0,0 +1,343 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web47command
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Schroeder
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mongoid
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">"
32
+ - !ruby/object:Gem::Version
33
+ version: '6'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '7'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">"
42
+ - !ruby/object:Gem::Version
43
+ version: '6'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: delayed_job_mongoid
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.3'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.3'
61
+ - !ruby/object:Gem::Dependency
62
+ name: web47core
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 0.4.5
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 0.4.5
75
+ - !ruby/object:Gem::Dependency
76
+ name: rails
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '4.2'
82
+ - - "<"
83
+ - !ruby/object:Gem::Version
84
+ version: '5.3'
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '4.2'
92
+ - - "<"
93
+ - !ruby/object:Gem::Version
94
+ version: '5.3'
95
+ - !ruby/object:Gem::Dependency
96
+ name: bundler
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: codacy-coverage
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: 2.1.0
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: 2.1.0
123
+ - !ruby/object:Gem::Dependency
124
+ name: database_cleaner
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ - !ruby/object:Gem::Dependency
138
+ name: listen
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ - !ruby/object:Gem::Dependency
152
+ name: minitest
153
+ requirement: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - '='
156
+ - !ruby/object:Gem::Version
157
+ version: 5.10.3
158
+ type: :development
159
+ prerelease: false
160
+ version_requirements: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - '='
163
+ - !ruby/object:Gem::Version
164
+ version: 5.10.3
165
+ - !ruby/object:Gem::Dependency
166
+ name: minitest-rails
167
+ requirement: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - '='
170
+ - !ruby/object:Gem::Version
171
+ version: 3.0.0
172
+ type: :development
173
+ prerelease: false
174
+ version_requirements: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - '='
177
+ - !ruby/object:Gem::Version
178
+ version: 3.0.0
179
+ - !ruby/object:Gem::Dependency
180
+ name: minitest-rails-capybara
181
+ requirement: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - '='
184
+ - !ruby/object:Gem::Version
185
+ version: 3.0.1
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - '='
191
+ - !ruby/object:Gem::Version
192
+ version: 3.0.1
193
+ - !ruby/object:Gem::Dependency
194
+ name: minitest-reporters
195
+ requirement: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ type: :development
201
+ prerelease: false
202
+ version_requirements: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ - !ruby/object:Gem::Dependency
208
+ name: mocha
209
+ requirement: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - ">="
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ type: :development
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ - !ruby/object:Gem::Dependency
222
+ name: shoulda
223
+ requirement: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - '='
226
+ - !ruby/object:Gem::Version
227
+ version: 3.6.0
228
+ type: :development
229
+ prerelease: false
230
+ version_requirements: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - '='
233
+ - !ruby/object:Gem::Version
234
+ version: 3.6.0
235
+ - !ruby/object:Gem::Dependency
236
+ name: shoulda-matchers
237
+ requirement: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - ">="
240
+ - !ruby/object:Gem::Version
241
+ version: '0'
242
+ type: :development
243
+ prerelease: false
244
+ version_requirements: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - ">="
247
+ - !ruby/object:Gem::Version
248
+ version: '0'
249
+ - !ruby/object:Gem::Dependency
250
+ name: simplecov
251
+ requirement: !ruby/object:Gem::Requirement
252
+ requirements:
253
+ - - '='
254
+ - !ruby/object:Gem::Version
255
+ version: 0.16.1
256
+ type: :development
257
+ prerelease: false
258
+ version_requirements: !ruby/object:Gem::Requirement
259
+ requirements:
260
+ - - '='
261
+ - !ruby/object:Gem::Version
262
+ version: 0.16.1
263
+ - !ruby/object:Gem::Dependency
264
+ name: test-unit
265
+ requirement: !ruby/object:Gem::Requirement
266
+ requirements:
267
+ - - ">="
268
+ - !ruby/object:Gem::Version
269
+ version: '0'
270
+ type: :development
271
+ prerelease: false
272
+ version_requirements: !ruby/object:Gem::Requirement
273
+ requirements:
274
+ - - ">="
275
+ - !ruby/object:Gem::Version
276
+ version: '0'
277
+ - !ruby/object:Gem::Dependency
278
+ name: vcr
279
+ requirement: !ruby/object:Gem::Requirement
280
+ requirements:
281
+ - - ">="
282
+ - !ruby/object:Gem::Version
283
+ version: '0'
284
+ type: :development
285
+ prerelease: false
286
+ version_requirements: !ruby/object:Gem::Requirement
287
+ requirements:
288
+ - - ">="
289
+ - !ruby/object:Gem::Version
290
+ version: '0'
291
+ - !ruby/object:Gem::Dependency
292
+ name: webmock
293
+ requirement: !ruby/object:Gem::Requirement
294
+ requirements:
295
+ - - ">="
296
+ - !ruby/object:Gem::Version
297
+ version: '0'
298
+ type: :development
299
+ prerelease: false
300
+ version_requirements: !ruby/object:Gem::Requirement
301
+ requirements:
302
+ - - ">="
303
+ - !ruby/object:Gem::Version
304
+ version: '0'
305
+ description: Support the execution and recordation of running commands in a shell
306
+ email:
307
+ - chris@app47.com
308
+ executables: []
309
+ extensions: []
310
+ extra_rdoc_files: []
311
+ files:
312
+ - LICENSE
313
+ - README.md
314
+ - lib/app/jobs/cron/trim_jobs.rb
315
+ - lib/app/models/command_job.rb
316
+ - lib/app/models/command_job_log.rb
317
+ - lib/app/models/job_log.rb
318
+ - lib/web47command.rb
319
+ - lib/web47command/version.rb
320
+ homepage: https://app47.com
321
+ licenses:
322
+ - MIT
323
+ metadata: {}
324
+ post_install_message:
325
+ rdoc_options: []
326
+ require_paths:
327
+ - lib
328
+ required_ruby_version: !ruby/object:Gem::Requirement
329
+ requirements:
330
+ - - "~>"
331
+ - !ruby/object:Gem::Version
332
+ version: 2.4.1
333
+ required_rubygems_version: !ruby/object:Gem::Requirement
334
+ requirements:
335
+ - - ">="
336
+ - !ruby/object:Gem::Version
337
+ version: '0'
338
+ requirements: []
339
+ rubygems_version: 3.0.8
340
+ signing_key:
341
+ specification_version: 4
342
+ summary: App47 Web Command Library.
343
+ test_files: []