web47command 0.0.1

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 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: []