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 +7 -0
- data/LICENSE +21 -0
- data/README.md +75 -0
- data/lib/app/jobs/cron/trim_jobs.rb +28 -0
- data/lib/app/models/command_job.rb +269 -0
- data/lib/app/models/command_job_log.rb +23 -0
- data/lib/app/models/job_log.rb +27 -0
- data/lib/web47command.rb +8 -0
- data/lib/web47command/version.rb +5 -0
- metadata +343 -0
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
|
data/lib/web47command.rb
ADDED
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: []
|