philbot 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/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Cheffile +10 -0
- data/Cheffile.lock +40 -0
- data/Gemfile +7 -0
- data/Guardfile +33 -0
- data/LICENSE.txt +22 -0
- data/Procfile +3 -0
- data/README.md +16 -0
- data/Rakefile +6 -0
- data/Vagrantfile +20 -0
- data/bin/philbot +6 -0
- data/conf/README.md +0 -0
- data/features/config.feature +40 -0
- data/features/crud/file_create.feature +59 -0
- data/features/crud/file_delete.feature +23 -0
- data/features/crud/file_update.feature +38 -0
- data/features/rackspace.feature +5 -0
- data/features/step_definitions/config.steps.rb +22 -0
- data/features/step_definitions/crud_steps.rb +69 -0
- data/features/support/async_support.rb +18 -0
- data/features/support/env.rb +25 -0
- data/features/support/hooks.rb +0 -0
- data/lib/philbot.rb +27 -0
- data/lib/philbot/config.rb +27 -0
- data/lib/philbot/monitors/admin_monitor.rb +15 -0
- data/lib/philbot/monitors/share_monitor.rb +30 -0
- data/lib/philbot/providers/rackspace.rb +22 -0
- data/lib/philbot/version.rb +3 -0
- data/lib/philbot/workers/destroyer.rb +16 -0
- data/lib/philbot/workers/uploader.rb +18 -0
- data/philbot.gemspec +38 -0
- data/spec/spec_helper.rb +20 -0
- metadata +314 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fab7c4edf1128773e3c70ee66d2c107fd733a5ed
|
4
|
+
data.tar.gz: f830370630a8d08c681f2a27f1ab734a135fecbc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 53ebf44b34cfe6b9b81f25066934280c893acd768f77c2a335ab7b591332f5d5f67101795fca080ca9be9a638ccb25c993b3028f4a53433b9db4107a4eba343b
|
7
|
+
data.tar.gz: ebda2315945d748d7d8c3c9b8f6086704be6f5dd2ae3ca45261fc76dcf5db595ca14da7bb3a49fe9806d21b28e2a4ae9c933c613b47e76b1ee7b3d89da1a3552
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
|
19
|
+
.idea/
|
20
|
+
conf/philbot.yaml
|
21
|
+
cookbooks
|
22
|
+
.vagrant
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Cheffile
ADDED
data/Cheffile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
SITE
|
2
|
+
remote: http://community.opscode.com/api/v1
|
3
|
+
specs:
|
4
|
+
build-essential (1.4.2)
|
5
|
+
chef-client (3.1.0)
|
6
|
+
cron (>= 1.2.0)
|
7
|
+
logrotate (>= 1.2.0)
|
8
|
+
chef_gem (0.1.0)
|
9
|
+
chef_handler (1.1.4)
|
10
|
+
cron (1.2.8)
|
11
|
+
dmg (2.0.8)
|
12
|
+
git (2.7.0)
|
13
|
+
build-essential (>= 0.0.0)
|
14
|
+
dmg (>= 0.0.0)
|
15
|
+
runit (>= 1.0.0)
|
16
|
+
windows (>= 0.0.0)
|
17
|
+
yum (>= 0.0.0)
|
18
|
+
logrotate (1.4.0)
|
19
|
+
runit (1.4.0)
|
20
|
+
build-essential (>= 0.0.0)
|
21
|
+
yum (>= 0.0.0)
|
22
|
+
samba (0.11.4)
|
23
|
+
windows (1.11.0)
|
24
|
+
chef_handler (>= 0.0.0)
|
25
|
+
yum (2.4.2)
|
26
|
+
|
27
|
+
GIT
|
28
|
+
remote: https://github.com/fnichol/chef-rvm
|
29
|
+
ref: master
|
30
|
+
sha: 0cd320911c1e568cbf2eeac895a5ef06e5821589
|
31
|
+
specs:
|
32
|
+
rvm (0.9.1)
|
33
|
+
chef_gem (>= 0.0.0)
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
chef-client (>= 0)
|
37
|
+
git (>= 0)
|
38
|
+
rvm (>= 0)
|
39
|
+
samba (>= 0)
|
40
|
+
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
notification :on
|
5
|
+
|
6
|
+
guard 'cucumber', :cli => '--format pretty' do
|
7
|
+
watch(%r{^lib/.+$}) { 'features' }
|
8
|
+
watch(%r{^features/.+\.feature$})
|
9
|
+
watch(%r{^features/support/.+$}) { 'features' }
|
10
|
+
watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
|
11
|
+
end
|
12
|
+
=begin
|
13
|
+
guard :rspec do
|
14
|
+
watch(%r{^spec/.+_spec\.rb$})
|
15
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
16
|
+
watch('spec/spec_helper.rb') { "spec" }
|
17
|
+
|
18
|
+
# Rails example
|
19
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
20
|
+
watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
21
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
22
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
23
|
+
watch('config/routes.rb') { "spec/routing" }
|
24
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
25
|
+
|
26
|
+
# Capybara features specs
|
27
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
|
28
|
+
|
29
|
+
# Turnip features and steps
|
30
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
31
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
32
|
+
end
|
33
|
+
=end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 The Open Data Institute
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Procfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
[](https://travis-ci.org/theodi/philbot)
|
2
|
+
[](https://codeclimate.com/github/theodi/philbot)
|
3
|
+
[](https://coveralls.io/r/theodi/philbot)
|
4
|
+
[](https://gemnasium.com/theodi/philbot)
|
5
|
+
|
6
|
+
#Philbot
|
7
|
+
|
8
|
+
Cloudfiles client
|
9
|
+
|
10
|
+
## Contributing
|
11
|
+
|
12
|
+
1. Fork it
|
13
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
14
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
15
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
16
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/Vagrantfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
VAGRANTFILE_API_VERSION = "2"
|
5
|
+
|
6
|
+
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
7
|
+
config.vm.box = "precise64"
|
8
|
+
config.vm.network :public_network
|
9
|
+
|
10
|
+
# config.vm.provision :chef_solo do |chef|
|
11
|
+
# chef.cookbooks_path = "cookbooks"
|
12
|
+
# chef.data_bags_path = "data_bags"
|
13
|
+
# chef.add_recipe "git"
|
14
|
+
# chef.add_recipe "rvm"
|
15
|
+
# chef.add_recipe "samba"
|
16
|
+
|
17
|
+
# You may also specify custom JSON attributes:
|
18
|
+
# chef.json = { :mysql_password => "foo" }
|
19
|
+
# end
|
20
|
+
end
|
data/bin/philbot
ADDED
data/conf/README.md
ADDED
File without changes
|
@@ -0,0 +1,40 @@
|
|
1
|
+
@config
|
2
|
+
Feature: Generate a config object
|
3
|
+
|
4
|
+
Scenario: Create config from file
|
5
|
+
Given a file named "conf/philbot.yaml" with:
|
6
|
+
"""
|
7
|
+
provider: Rackspace
|
8
|
+
username: fake_philbot_user
|
9
|
+
api_key: fake_philbot_key
|
10
|
+
region: lon
|
11
|
+
container: philbot
|
12
|
+
"""
|
13
|
+
When I create a new Philbot::Config object using file "conf/philbot.yaml"
|
14
|
+
Then looking up "username" on the object should yield "fake_philbot_user"
|
15
|
+
And looking up "container" on the object should yield "philbot"
|
16
|
+
|
17
|
+
Scenario: Config object should be a singleton
|
18
|
+
|
19
|
+
Scenario: Config should reconfigure itself when config file changes
|
20
|
+
Given a dummy config object
|
21
|
+
When the admin monitor is watching "conf/"
|
22
|
+
Then looking up "username" on the object should yield "fake_philbot_user"
|
23
|
+
When I wait for the monitor to notice
|
24
|
+
|
25
|
+
Given a file named "conf/philbot.yaml" with:
|
26
|
+
"""
|
27
|
+
provider: Rackspace
|
28
|
+
username: some_other_name
|
29
|
+
api_key: some_other_key
|
30
|
+
region: dfw
|
31
|
+
container: philbot
|
32
|
+
"""
|
33
|
+
When I wait for the monitor to notice
|
34
|
+
Then looking up "username" on the object should yield "some_other_name"
|
35
|
+
# When I create a new Philbot::Config object using file "conf/philbot.yaml"
|
36
|
+
# Then looking up "username" on the object should yield "some_other_name"
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
@create
|
2
|
+
Feature: Upload file on create
|
3
|
+
|
4
|
+
Scenario: Queue file when created
|
5
|
+
Given a directory named "watchme/"
|
6
|
+
Then the upload of file "file_01" should be queued
|
7
|
+
When the share monitor is watching "watchme/"
|
8
|
+
And I write to "watchme/file_01" with:
|
9
|
+
"""
|
10
|
+
RICHARD PRYOR
|
11
|
+
"""
|
12
|
+
And I wait for the monitor to notice
|
13
|
+
|
14
|
+
Scenario: Upload file when queued
|
15
|
+
Given a directory named "watchme/"
|
16
|
+
And a 512 byte file named "watchme/file_02"
|
17
|
+
And the upload of "file_02" has been queued
|
18
|
+
Then the 512 byte file "file_02" should be uploaded
|
19
|
+
When the queued job is executed
|
20
|
+
|
21
|
+
Scenario: Dotfiles should not be uploaded
|
22
|
+
Given a directory named "watchme/"
|
23
|
+
Then the upload of file ".dot_file" should not be queued
|
24
|
+
When the share monitor is watching "watchme/"
|
25
|
+
And I write to "watchme/.dot_file" with:
|
26
|
+
"""
|
27
|
+
REGGIE WATTS
|
28
|
+
"""
|
29
|
+
And I wait for the monitor to notice
|
30
|
+
|
31
|
+
Scenario: Dotfiles inside subdirs should not be uploaded
|
32
|
+
Given a directory named "watchme/"
|
33
|
+
Then the upload of file "subdir/.dot_file" should not be queued
|
34
|
+
When the share monitor is watching "watchme/"
|
35
|
+
And I write to "watchme/subdir/.dot_file" with:
|
36
|
+
"""
|
37
|
+
LOUIS CK
|
38
|
+
"""
|
39
|
+
And I wait for the monitor to notice
|
40
|
+
|
41
|
+
Scenario: Queue and upload with complete (relative) path
|
42
|
+
Given a directory named "watchme/"
|
43
|
+
Then the upload of file "subdir/file_03" should be queued
|
44
|
+
When the share monitor is watching "watchme/"
|
45
|
+
And I write to "watchme/subdir/file_03" with:
|
46
|
+
"""
|
47
|
+
MITCH HEDBERG
|
48
|
+
"""
|
49
|
+
And I wait for the monitor to notice
|
50
|
+
|
51
|
+
Scenario: Queue file when created and do the right thing with trailing slash
|
52
|
+
Given a directory named "watchme/"
|
53
|
+
Then the upload of file "file_04" should be queued
|
54
|
+
When the share monitor is watching "watchme/" including the trailing slash
|
55
|
+
And I write to "watchme/file_04" with:
|
56
|
+
"""
|
57
|
+
GEORGE CARLIN
|
58
|
+
"""
|
59
|
+
And I wait for the monitor to notice
|
@@ -0,0 +1,23 @@
|
|
1
|
+
@delete
|
2
|
+
Feature: Delete remote file on local delete
|
3
|
+
|
4
|
+
Scenario: Delete remote file when deleted locally
|
5
|
+
Given a directory named "watchme/"
|
6
|
+
Then the upload of file "file_01" should be queued
|
7
|
+
When the share monitor is watching "watchme/"
|
8
|
+
And I write to "watchme/file_01" with:
|
9
|
+
"""
|
10
|
+
BILL HICKS
|
11
|
+
"""
|
12
|
+
And I wait for the monitor to notice
|
13
|
+
And the upload of "file_01" has been queued
|
14
|
+
Then the 10 byte file "file_01" should be uploaded
|
15
|
+
When the queued job is executed
|
16
|
+
|
17
|
+
And the deletion of file "file_01" should be queued
|
18
|
+
When I remove the file "watchme/file_01"
|
19
|
+
And I wait for the monitor to notice
|
20
|
+
|
21
|
+
And the deletion of remote file "file_01" has been queued
|
22
|
+
Then the remote file "file_01" should be deleted
|
23
|
+
When the queued job is executed
|
@@ -0,0 +1,38 @@
|
|
1
|
+
@update
|
2
|
+
Feature: Upload file on change
|
3
|
+
|
4
|
+
Scenario: Queue file when changed
|
5
|
+
Given a directory named "watchme/"
|
6
|
+
Then the upload of file "file_01" should be queued 2 times
|
7
|
+
When the share monitor is watching "watchme/"
|
8
|
+
Given a file named "watchme/file_01" with:
|
9
|
+
"""
|
10
|
+
MORECAMBE
|
11
|
+
|
12
|
+
"""
|
13
|
+
And I wait for the monitor to notice
|
14
|
+
And I append to "watchme/file_01" with:
|
15
|
+
"""
|
16
|
+
WISE
|
17
|
+
|
18
|
+
"""
|
19
|
+
And I wait for the monitor to notice
|
20
|
+
|
21
|
+
Scenario: Update file when changed
|
22
|
+
Given a directory named "watchme/"
|
23
|
+
And a file named "watchme/file_02" with:
|
24
|
+
"""
|
25
|
+
DEREK
|
26
|
+
|
27
|
+
"""
|
28
|
+
And the upload of "file_02" has been queued
|
29
|
+
Then the 6 byte file "file_02" should be uploaded
|
30
|
+
When the queued job is executed
|
31
|
+
And I append to "watchme/file_02" with:
|
32
|
+
"""
|
33
|
+
CLIVE
|
34
|
+
|
35
|
+
"""
|
36
|
+
And the upload of "file_02" has been queued
|
37
|
+
Then the 12 byte file "file_02" should be uploaded
|
38
|
+
When the queued job is executed
|
@@ -0,0 +1,22 @@
|
|
1
|
+
When(/^I create a new Philbot::Config object using file "(.*?)"$/) do |yaml|
|
2
|
+
Philbot::Config.instance.configure full_path yaml
|
3
|
+
end
|
4
|
+
|
5
|
+
Then(/^looking up "(.*?)" on the object should yield "(.*?)"$/) do |key, value|
|
6
|
+
eventually { Philbot::Config.instance[key].should == value }
|
7
|
+
end
|
8
|
+
|
9
|
+
Given(/^a dummy config object$/) do
|
10
|
+
steps %{
|
11
|
+
Given a file named "conf/philbot.yaml" with:
|
12
|
+
"""
|
13
|
+
provider: Rackspace
|
14
|
+
username: fake_philbot_user
|
15
|
+
api_key: fake_philbot_key
|
16
|
+
region: lon
|
17
|
+
container: philbot
|
18
|
+
"""
|
19
|
+
When I create a new Philbot::Config object using file "conf/philbot.yaml"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
When(/^the (.*) monitor is watching "(.*?)"( including the trailing slash)?$/) do |monitor, directory, boolean|
|
2
|
+
trailing = ''
|
3
|
+
if boolean
|
4
|
+
trailing = '/'
|
5
|
+
end
|
6
|
+
s = 'Philbot::Monitors::%sMonitor' % monitor.capitalize
|
7
|
+
Kernel.const_get(s).run full_path(directory + trailing)
|
8
|
+
end
|
9
|
+
|
10
|
+
Then(/^the upload of file "(.*?)" should( not)? be queued$/) do |filename, boolean|
|
11
|
+
if boolean
|
12
|
+
eventually { Resque.should_not_receive(:enqueue).with(Philbot::Workers::Uploader, [filename]) }
|
13
|
+
else
|
14
|
+
eventually { Resque.should_receive(:enqueue).with(Philbot::Workers::Uploader, [filename]).once }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Then(/^the upload of file "(.*?)" should be queued (\d+) times$/) do |filename, count|
|
19
|
+
eventually { Resque.should_receive(:enqueue).with(Philbot::Workers::Uploader, [filename]).exactly(count.to_i).times }
|
20
|
+
end
|
21
|
+
|
22
|
+
When(/^I wait for the monitor to notice$/) do
|
23
|
+
sleep 2
|
24
|
+
end
|
25
|
+
|
26
|
+
Given(/^the upload of "(.*?)" has been queued$/) do |filename|
|
27
|
+
@files ||= []
|
28
|
+
@files << filename
|
29
|
+
@job = Philbot::Workers::Uploader
|
30
|
+
end
|
31
|
+
|
32
|
+
Given(/^the deletion of remote file "(.*?)" has been queued$/) do |filename|
|
33
|
+
@files ||= []
|
34
|
+
@files << filename
|
35
|
+
@job = Philbot::Workers::Destroyer
|
36
|
+
end
|
37
|
+
|
38
|
+
Then(/^the deletion of file "(.*?)" should be queued$/) do |filename|
|
39
|
+
eventually { Resque.should_receive(:enqueue).with(Philbot::Workers::Destroyer, [filename]).once }
|
40
|
+
end
|
41
|
+
|
42
|
+
Then(/^the (\d+) byte file "(.*?)" should be uploaded$/) do |size, filename|
|
43
|
+
rackspace = Object.new
|
44
|
+
Fog::Storage.should_receive(:new).and_return(rackspace)
|
45
|
+
|
46
|
+
directory = Object.new
|
47
|
+
rackspace.stub_chain(:directories, :get).and_return(directory)
|
48
|
+
|
49
|
+
directory.stub_chain(:files, :create) do |options|
|
50
|
+
options[:key].should == filename
|
51
|
+
options[:body].size.should == size.to_i
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Then(/^the remote file "(.*?)" should be deleted$/) do |filename|
|
56
|
+
rackspace = Object.new
|
57
|
+
Fog::Storage.should_receive(:new).and_return(rackspace)
|
58
|
+
|
59
|
+
directory = Object.new
|
60
|
+
rackspace.stub_chain(:directories, :get).and_return(directory)
|
61
|
+
|
62
|
+
directory.stub_chain(:files, :destroy) do |fname|
|
63
|
+
fname.should == filename
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
When(/^the queued job is executed$/) do
|
68
|
+
@job.perform @files
|
69
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AsyncSupport
|
2
|
+
def eventually
|
3
|
+
timeout = 10
|
4
|
+
polling_interval = 0.2
|
5
|
+
time_limit = Time.now + timeout
|
6
|
+
loop do
|
7
|
+
begin
|
8
|
+
yield
|
9
|
+
rescue Exception => error
|
10
|
+
end
|
11
|
+
return if error.nil?
|
12
|
+
raise error if Time.now >= time_limit
|
13
|
+
sleep polling_interval
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
World(AsyncSupport)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'simplecov-rcov'
|
3
|
+
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
|
4
|
+
SimpleCov.start
|
5
|
+
|
6
|
+
require 'coveralls'
|
7
|
+
Coveralls.wear!
|
8
|
+
|
9
|
+
require 'aruba/cucumber'
|
10
|
+
|
11
|
+
$: << File.expand_path('../../lib', File.dirname(__FILE__))
|
12
|
+
require 'philbot'
|
13
|
+
|
14
|
+
require 'cucumber/rspec/doubles'
|
15
|
+
|
16
|
+
require 'resque/mock'
|
17
|
+
Resque.mock!
|
18
|
+
|
19
|
+
def aruba_tmp
|
20
|
+
File.join %w{tmp aruba}
|
21
|
+
end
|
22
|
+
|
23
|
+
def full_path filename
|
24
|
+
File.expand_path File.join aruba_tmp, filename
|
25
|
+
end
|
File without changes
|
data/lib/philbot.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'resque'
|
2
|
+
require 'listen'
|
3
|
+
require 'fog'
|
4
|
+
|
5
|
+
require 'philbot/version'
|
6
|
+
require 'philbot/config'
|
7
|
+
require 'philbot/workers/uploader'
|
8
|
+
require 'philbot/workers/destroyer'
|
9
|
+
require 'philbot/providers/rackspace'
|
10
|
+
require 'philbot/monitors/share_monitor'
|
11
|
+
require 'philbot/monitors/admin_monitor'
|
12
|
+
|
13
|
+
module Philbot
|
14
|
+
def self.configure yaml = 'conf/philbot.yaml'
|
15
|
+
Philbot::Config.instance.configure yaml
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.run watchdir, confdir
|
19
|
+
Philbot::Monitors::ShareMonitor.run watchdir
|
20
|
+
# Philbot::Monitors::AdminMonitor.run confdir
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.work
|
24
|
+
worker = Resque::Worker.new '*'
|
25
|
+
worker.work 5
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module Philbot
|
5
|
+
class Config
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def self.root
|
9
|
+
@@root
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.root= value
|
13
|
+
@@root = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure yaml_file = 'conf/philbot.yaml'
|
17
|
+
@yaml_file = yaml_file
|
18
|
+
|
19
|
+
y = YAML.load File.open yaml_file
|
20
|
+
@options = y
|
21
|
+
end
|
22
|
+
|
23
|
+
def [] key
|
24
|
+
@options[key]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Philbot
|
2
|
+
module Monitors
|
3
|
+
class AdminMonitor
|
4
|
+
def self.run directory
|
5
|
+
@@listener = Listen.to directory do |modified, added, removed|
|
6
|
+
unless modified.empty?
|
7
|
+
Philbot::Config.instance.configure modified[0]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
@@listener.start
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Philbot
|
2
|
+
module Monitors
|
3
|
+
class ShareMonitor
|
4
|
+
def self.run watchdir
|
5
|
+
Philbot::Config.root = File.expand_path(watchdir)
|
6
|
+
|
7
|
+
@@listener = Listen.to watchdir do |modified, added, removed|
|
8
|
+
unless added.empty?
|
9
|
+
Resque.enqueue Philbot::Workers::Uploader, mapit(added, watchdir)
|
10
|
+
end
|
11
|
+
|
12
|
+
unless modified.empty?
|
13
|
+
Resque.enqueue Philbot::Workers::Uploader, mapit(modified, watchdir)
|
14
|
+
end
|
15
|
+
|
16
|
+
unless removed.empty?
|
17
|
+
Resque.enqueue Philbot::Workers::Destroyer, mapit(removed, watchdir)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@@listener.start
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.mapit list, parentdir
|
24
|
+
pd = parentdir.gsub(/\/$/, '')
|
25
|
+
list.delete_if { |i| File.basename(i)[0] == '.' }
|
26
|
+
list.map { |i| i.gsub('%s/' % pd, '') }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Philbot
|
2
|
+
module Providers
|
3
|
+
class Rackspace
|
4
|
+
def dir
|
5
|
+
config = Philbot::Config.instance
|
6
|
+
|
7
|
+
rackspace = Fog::Storage.new(
|
8
|
+
{
|
9
|
+
provider: config['provider'],
|
10
|
+
rackspace_username: config['username'],
|
11
|
+
rackspace_api_key: config['api_key'],
|
12
|
+
rackspace_region: config['region'].to_sym
|
13
|
+
}
|
14
|
+
)
|
15
|
+
|
16
|
+
container = rackspace.directories.get config['container']
|
17
|
+
|
18
|
+
container
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'fog/rackspace/models/storage/files'
|
2
|
+
|
3
|
+
module Philbot
|
4
|
+
module Workers
|
5
|
+
class Destroyer
|
6
|
+
@queue = :default
|
7
|
+
|
8
|
+
def self.perform filenames
|
9
|
+
dir = Philbot::Providers::Rackspace.new.dir
|
10
|
+
filenames.each do |filename|
|
11
|
+
dir.files.destroy filename
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fog/rackspace/models/storage/files'
|
2
|
+
|
3
|
+
module Philbot
|
4
|
+
module Workers
|
5
|
+
class Uploader
|
6
|
+
@queue = :default
|
7
|
+
|
8
|
+
def self.perform filenames
|
9
|
+
dir = Philbot::Providers::Rackspace.new.dir
|
10
|
+
filenames.each do |filename|
|
11
|
+
# unless filename[0] == '.'
|
12
|
+
dir.files.create :key => filename, :body => File.open(File.join(Philbot::Config.root, filename))
|
13
|
+
# end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/philbot.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'philbot/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'philbot'
|
8
|
+
spec.version = Philbot::VERSION
|
9
|
+
spec.authors = ['pikesley']
|
10
|
+
spec.email = ['github@orgraphone.org']
|
11
|
+
spec.description = %q{Cloudfiles client}
|
12
|
+
spec.summary = %q{Cloudfiles client}
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'resque', '~> 1.2'
|
22
|
+
spec.add_dependency 'fog', '~> 1.18'
|
23
|
+
spec.add_dependency 'foreman'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.1'
|
27
|
+
spec.add_development_dependency 'cucumber', '~> 1.3'
|
28
|
+
spec.add_development_dependency 'guard-cucumber', '~> 1.4'
|
29
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.0'
|
30
|
+
spec.add_development_dependency 'terminal-notifier-guard'
|
31
|
+
spec.add_development_dependency 'simplecov-rcov', '~> 0.2'
|
32
|
+
spec.add_development_dependency 'aruba', '~> 0.5'
|
33
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
34
|
+
spec.add_development_dependency 'resque-mock', '~> 0.1'
|
35
|
+
spec.add_development_dependency 'coveralls', '~> 0.7'
|
36
|
+
spec.add_development_dependency 'unf', '~> 0.1'
|
37
|
+
spec.add_development_dependency 'librarian-chef'
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
5
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
6
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
7
|
+
# loaded once.
|
8
|
+
#
|
9
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
|
15
|
+
# Run specs in random order to surface order dependencies. If you find an
|
16
|
+
# order dependency and want to debug it, you can fix the order by providing
|
17
|
+
# the seed, which is printed after each run.
|
18
|
+
# --seed 1234
|
19
|
+
config.order = 'random'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,314 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: philbot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- pikesley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: resque
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fog
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.18'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.18'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: foreman
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: cucumber
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-cucumber
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.4'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.4'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: guard-rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: terminal-notifier-guard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov-rcov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ~>
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.2'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ~>
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.2'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: aruba
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ~>
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0.5'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ~>
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0.5'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ~>
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '2.14'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ~>
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '2.14'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: resque-mock
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ~>
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0.1'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ~>
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0.1'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: coveralls
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ~>
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0.7'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ~>
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0.7'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: unf
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ~>
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0.1'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ~>
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0.1'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: librarian-chef
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - '>='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - '>='
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
description: Cloudfiles client
|
238
|
+
email:
|
239
|
+
- github@orgraphone.org
|
240
|
+
executables:
|
241
|
+
- philbot
|
242
|
+
extensions: []
|
243
|
+
extra_rdoc_files: []
|
244
|
+
files:
|
245
|
+
- .gitignore
|
246
|
+
- .rspec
|
247
|
+
- .travis.yml
|
248
|
+
- Cheffile
|
249
|
+
- Cheffile.lock
|
250
|
+
- Gemfile
|
251
|
+
- Guardfile
|
252
|
+
- LICENSE.txt
|
253
|
+
- Procfile
|
254
|
+
- README.md
|
255
|
+
- Rakefile
|
256
|
+
- Vagrantfile
|
257
|
+
- bin/philbot
|
258
|
+
- conf/README.md
|
259
|
+
- features/config.feature
|
260
|
+
- features/crud/file_create.feature
|
261
|
+
- features/crud/file_delete.feature
|
262
|
+
- features/crud/file_update.feature
|
263
|
+
- features/rackspace.feature
|
264
|
+
- features/step_definitions/config.steps.rb
|
265
|
+
- features/step_definitions/crud_steps.rb
|
266
|
+
- features/support/async_support.rb
|
267
|
+
- features/support/env.rb
|
268
|
+
- features/support/hooks.rb
|
269
|
+
- lib/philbot.rb
|
270
|
+
- lib/philbot/config.rb
|
271
|
+
- lib/philbot/monitors/admin_monitor.rb
|
272
|
+
- lib/philbot/monitors/share_monitor.rb
|
273
|
+
- lib/philbot/providers/rackspace.rb
|
274
|
+
- lib/philbot/version.rb
|
275
|
+
- lib/philbot/workers/destroyer.rb
|
276
|
+
- lib/philbot/workers/uploader.rb
|
277
|
+
- philbot.gemspec
|
278
|
+
- spec/spec_helper.rb
|
279
|
+
homepage: ''
|
280
|
+
licenses:
|
281
|
+
- MIT
|
282
|
+
metadata: {}
|
283
|
+
post_install_message:
|
284
|
+
rdoc_options: []
|
285
|
+
require_paths:
|
286
|
+
- lib
|
287
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
288
|
+
requirements:
|
289
|
+
- - '>='
|
290
|
+
- !ruby/object:Gem::Version
|
291
|
+
version: '0'
|
292
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
293
|
+
requirements:
|
294
|
+
- - '>='
|
295
|
+
- !ruby/object:Gem::Version
|
296
|
+
version: '0'
|
297
|
+
requirements: []
|
298
|
+
rubyforge_project:
|
299
|
+
rubygems_version: 2.1.11
|
300
|
+
signing_key:
|
301
|
+
specification_version: 4
|
302
|
+
summary: Cloudfiles client
|
303
|
+
test_files:
|
304
|
+
- features/config.feature
|
305
|
+
- features/crud/file_create.feature
|
306
|
+
- features/crud/file_delete.feature
|
307
|
+
- features/crud/file_update.feature
|
308
|
+
- features/rackspace.feature
|
309
|
+
- features/step_definitions/config.steps.rb
|
310
|
+
- features/step_definitions/crud_steps.rb
|
311
|
+
- features/support/async_support.rb
|
312
|
+
- features/support/env.rb
|
313
|
+
- features/support/hooks.rb
|
314
|
+
- spec/spec_helper.rb
|