cluster-fuck 0.1.0
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.
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +13 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +52 -0
- data/Rakefile +10 -0
- data/bin/clusterfuck +46 -0
- data/cluster-fuck.gemspec +25 -0
- data/lib/cluster-fuck.rb +25 -0
- data/lib/cluster-fuck/cli.rb +5 -0
- data/lib/cluster-fuck/commands/create.rb +20 -0
- data/lib/cluster-fuck/commands/edit.rb +28 -0
- data/lib/cluster-fuck/commands/list.rb +14 -0
- data/lib/cluster-fuck/commands/override.rb +27 -0
- data/lib/cluster-fuck/configuration.rb +13 -0
- data/lib/cluster-fuck/credential_grabber.rb +39 -0
- data/lib/cluster-fuck/reader.rb +44 -0
- data/lib/cluster-fuck/s3_methods.rb +42 -0
- data/lib/cluster-fuck/version.rb +3 -0
- data/lib/cluster-fuck/writer.rb +30 -0
- data/spec/lib/cluster-fuck/commands/create_spec.rb +41 -0
- data/spec/lib/cluster-fuck/commands/edit_spec.rb +48 -0
- data/spec/lib/cluster-fuck/commands/override_spec.rb +26 -0
- data/spec/lib/cluster-fuck/configuration_spec.rb +25 -0
- data/spec/lib/cluster-fuck/credential_grabber_spec.rb +81 -0
- data/spec/lib/cluster-fuck/reader_spec.rb +59 -0
- data/spec/lib/cluster-fuck/s3_methods_spec.rb +35 -0
- data/spec/lib/cluster-fuck/writer_spec.rb +83 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/amicus_env_setter.rb +6 -0
- metadata +152 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.3@cluster-fuck --create
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
11
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
+
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"] }
|
13
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
14
|
+
watch('config/routes.rb') { "spec/routing" }
|
15
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
16
|
+
|
17
|
+
# Capybara features specs
|
18
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
|
19
|
+
|
20
|
+
# Turnip features and steps
|
21
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
22
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
23
|
+
end
|
24
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Topper Bowers
|
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/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Cluster::Fuck
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'cluster-fuck'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install cluster-fuck
|
18
|
+
|
19
|
+
|
20
|
+
### setup your credential file
|
21
|
+
If you are not on an EC2 instance then you should setup your ~/.cluster-fuck file with the followng yaml:
|
22
|
+
|
23
|
+
```yaml
|
24
|
+
:access_key_id: access_key
|
25
|
+
:secret_access_key: secret_key
|
26
|
+
```
|
27
|
+
|
28
|
+
It will also look for a ~/.fog file with the following syntax
|
29
|
+
|
30
|
+
```yaml
|
31
|
+
:default:
|
32
|
+
:aws_access_key_id: access_key
|
33
|
+
:aws_secret_access_key: secret_key
|
34
|
+
```
|
35
|
+
|
36
|
+
Otherwise, it expects you to be on an EC2 instance and not have to setup credentials.
|
37
|
+
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
reader = ClusterFuck::Reader.new
|
43
|
+
reader[:stripe][:api_key] loads "config_bucket/amicus_env/stripe and returns the api_key from the hash
|
44
|
+
```
|
45
|
+
The ClusterFuck::Reader instance will automatically load the configuration for
|
46
|
+
the environment stored in the AMICUS_ENV environment variable on the host.
|
47
|
+
|
48
|
+
1. Fork it
|
49
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
52
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'ci/reporter/rake/rspec'
|
8
|
+
rescue LoadError => ex
|
9
|
+
puts "Failed to load the ci_reporter gem. Make sure it is available in your loadpaths"
|
10
|
+
end
|
data/bin/clusterfuck
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'commander/import'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.join(File.expand_path(File.dirname(__FILE__)), "../lib"))
|
5
|
+
require "cluster-fuck/cli"
|
6
|
+
|
7
|
+
program :version, ClusterFuck::VERSION
|
8
|
+
program :description, 'Amicus environment-aware configuration manager'
|
9
|
+
|
10
|
+
#TODO: (topper) - fill in the help stuff
|
11
|
+
|
12
|
+
command :edit do |c|
|
13
|
+
c.syntax = 'clusterfuck edit [options]'
|
14
|
+
c.summary = ''
|
15
|
+
c.description = ''
|
16
|
+
c.example 'description', 'command example'
|
17
|
+
c.option '--force', 'force edit even if local file present'
|
18
|
+
c.when_called ClusterFuck::Commands::Edit, :run_command
|
19
|
+
end
|
20
|
+
|
21
|
+
command :create do |c|
|
22
|
+
c.syntax = 'clusterfuck create [options]'
|
23
|
+
c.summary = ''
|
24
|
+
c.description = ''
|
25
|
+
c.example 'description', 'command example'
|
26
|
+
#c.option '--some-switch', 'Some switch that does something'
|
27
|
+
c.when_called ClusterFuck::Commands::Create, :run_command
|
28
|
+
end
|
29
|
+
|
30
|
+
command :list do |c|
|
31
|
+
c.syntax = 'clusterfuck list'
|
32
|
+
c.summary = ''
|
33
|
+
c.description = ''
|
34
|
+
c.example 'description', 'command example'
|
35
|
+
#c.option '--some-switch', 'Some switch that does something'
|
36
|
+
c.when_called ClusterFuck::Commands::List, :run_command
|
37
|
+
end
|
38
|
+
|
39
|
+
command :override do |c|
|
40
|
+
c.syntax = 'clusterfuck override [options]'
|
41
|
+
c.summary = ''
|
42
|
+
c.description = ''
|
43
|
+
c.example 'description', 'command example'
|
44
|
+
#c.option '--some-switch', 'Some switch that does something'
|
45
|
+
c.when_called ClusterFuck::Commands::Override, :run_command
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cluster-fuck/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "cluster-fuck"
|
8
|
+
gem.version = ClusterFuck::VERSION
|
9
|
+
gem.authors = ["Topper Bowers"]
|
10
|
+
gem.email = ["topper@amicushq.com"]
|
11
|
+
gem.description = "cluster-aware S3 and ZK based config getter/setter"
|
12
|
+
gem.summary = ""
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.add_dependency "aws-sdk"
|
16
|
+
gem.add_dependency "hashie"
|
17
|
+
gem.add_dependency "commander", "~> 4.1.0"
|
18
|
+
|
19
|
+
gem.add_development_dependency "rspec"
|
20
|
+
|
21
|
+
gem.files = `git ls-files`.split($/)
|
22
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
23
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
24
|
+
gem.require_paths = ["lib"]
|
25
|
+
end
|
data/lib/cluster-fuck.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "cluster-fuck/version"
|
2
|
+
|
3
|
+
module ClusterFuck
|
4
|
+
CONFIG_BUCKET = 'amicus-config'
|
5
|
+
AMICUS_ENV = ENV['AMICUS_ENV'] || 'development'
|
6
|
+
|
7
|
+
def self.logger=(logger)
|
8
|
+
@logger = logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.logger
|
12
|
+
@logger ||= Logger.new($stdout)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'aws-sdk'
|
18
|
+
require 'hashie'
|
19
|
+
require 'yaml'
|
20
|
+
|
21
|
+
require 'cluster-fuck/credential_grabber'
|
22
|
+
require 'cluster-fuck/s3_methods'
|
23
|
+
require 'cluster-fuck/configuration'
|
24
|
+
require 'cluster-fuck/reader'
|
25
|
+
require 'cluster-fuck/writer'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'commander'
|
2
|
+
require_relative "edit"
|
3
|
+
|
4
|
+
module ClusterFuck
|
5
|
+
module Commands
|
6
|
+
class Create
|
7
|
+
include ClusterFuck::S3Methods
|
8
|
+
|
9
|
+
attr_reader :key
|
10
|
+
def run_command(args, options = {})
|
11
|
+
@key = args.first
|
12
|
+
obj = s3_object(key)
|
13
|
+
raise ConflictError, "#{key} already exists!" if obj.exists?
|
14
|
+
obj.write('')
|
15
|
+
Edit.new.run_command(args)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'commander'
|
2
|
+
|
3
|
+
module ClusterFuck
|
4
|
+
module Commands
|
5
|
+
class Edit
|
6
|
+
include Commander::UI
|
7
|
+
|
8
|
+
attr_reader :key, :options
|
9
|
+
def run_command(args, options = Hashie::Mash.new)
|
10
|
+
@key = args.first
|
11
|
+
@options = options
|
12
|
+
raise ArgumentError, "File #{key} is overridden locally! use --force to force" if reader.has_local_override? and !options.force
|
13
|
+
|
14
|
+
new_yaml = ask_editor(YAML.dump(reader.read(remote_only: true).to_hash))
|
15
|
+
writer.set(Configuration.from_yaml(new_yaml), reader.version_count)
|
16
|
+
end
|
17
|
+
|
18
|
+
def writer
|
19
|
+
@writer ||= ClusterFuck::Writer.new(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def reader
|
23
|
+
@reader ||= ClusterFuck::Reader.new(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'commander'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module ClusterFuck
|
5
|
+
module Commands
|
6
|
+
class Override
|
7
|
+
include ClusterFuck::S3Methods
|
8
|
+
|
9
|
+
attr_reader :key
|
10
|
+
def run_command(args, options = {})
|
11
|
+
@key = args.first
|
12
|
+
contents = reader.read(remote_only: true)
|
13
|
+
FileUtils.mkdir_p(File.dirname(reader.local_override_path))
|
14
|
+
File.open(reader.local_override_path, "w") do |f|
|
15
|
+
f << contents.to_yaml
|
16
|
+
end
|
17
|
+
$stdout.puts("wrote #{reader.local_override_path} with contents of #{reader.full_s3_path(key)}")
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def reader
|
22
|
+
@reader ||= ClusterFuck::Reader.new(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ClusterFuck
|
2
|
+
|
3
|
+
class CredentialGrabber
|
4
|
+
FOG_PATH = "~/.fog"
|
5
|
+
CF_PATH = "~/.cluster-fuck"
|
6
|
+
|
7
|
+
def self.find
|
8
|
+
new.find
|
9
|
+
end
|
10
|
+
|
11
|
+
def find
|
12
|
+
from_cluster_fuck_file || from_fog_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_fog_file
|
16
|
+
if exists?(File.expand_path(FOG_PATH))
|
17
|
+
fog_credentials = YAML.load_file(File.expand_path(FOG_PATH))
|
18
|
+
{
|
19
|
+
access_key_id: fog_credentials[:default][:aws_access_key_id],
|
20
|
+
secret_access_key: fog_credentials[:default][:aws_secret_access_key],
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def from_cluster_fuck_file
|
26
|
+
if exists?(CF_PATH)
|
27
|
+
YAML.load_file(CF_PATH)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def exists?(path)
|
33
|
+
File.exist?(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ClusterFuck
|
2
|
+
class Reader
|
3
|
+
include S3Methods
|
4
|
+
|
5
|
+
LOCAL_OVERRIDE_DIR = "cluster-fuck"
|
6
|
+
|
7
|
+
attr_reader :amicus_env, :key, :version_count
|
8
|
+
def initialize(key, opts={})
|
9
|
+
@key = key
|
10
|
+
@amicus_env = opts[:amicus_env] || ClusterFuck::AMICUS_ENV
|
11
|
+
@ignore_local = opts[:ignore_local]
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(opts = {})
|
15
|
+
yaml = data_for_key(opts)
|
16
|
+
if yaml
|
17
|
+
@version_count = stored_object.versions.count unless has_local_override?
|
18
|
+
Configuration.from_yaml(yaml)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_local_override?
|
23
|
+
File.exists?(local_override_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def local_override_path
|
27
|
+
File.join(LOCAL_OVERRIDE_DIR, full_s3_path(key))
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def data_for_key(opts = {})
|
32
|
+
if has_local_override? and !opts[:remote_only]
|
33
|
+
File.read(local_override_path)
|
34
|
+
else
|
35
|
+
stored_object.read
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def stored_object
|
40
|
+
@stored_object ||= s3_object(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ClusterFuck
|
2
|
+
module S3Methods
|
3
|
+
|
4
|
+
class ConflictError < StandardError; end
|
5
|
+
|
6
|
+
def s3_object(object_name)
|
7
|
+
bucket.objects[full_s3_path(object_name)]
|
8
|
+
end
|
9
|
+
|
10
|
+
def bucket
|
11
|
+
@bucket ||= s3.buckets[CONFIG_BUCKET]
|
12
|
+
end
|
13
|
+
|
14
|
+
def credentials
|
15
|
+
@credentials ||= CredentialGrabber.find
|
16
|
+
end
|
17
|
+
|
18
|
+
def s3
|
19
|
+
if credentials
|
20
|
+
AWS::S3.new(credentials) #could be nil, especially if on EC2
|
21
|
+
else
|
22
|
+
AWS::S3.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_files
|
27
|
+
bucket.objects.with_prefix(amicus_env).collect(&:key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def full_s3_path(key)
|
31
|
+
"#{amicus_env}/#{key}"
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
|
37
|
+
def amicus_env
|
38
|
+
AMICUS_ENV
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ClusterFuck
|
2
|
+
class Writer
|
3
|
+
include S3Methods
|
4
|
+
|
5
|
+
attr_reader :amicus_env, :key
|
6
|
+
def initialize(key, opts = {})
|
7
|
+
@key = key
|
8
|
+
@amicus_env = opts[:amicus_env] || ClusterFuck::AMICUS_ENV
|
9
|
+
end
|
10
|
+
|
11
|
+
#todo, thread safety and process safety please
|
12
|
+
def set(val, version_count = nil)
|
13
|
+
raise_unless_version_count_is_good(version_count)
|
14
|
+
stored_object.write(val.to_yaml)
|
15
|
+
raise_unless_version_count_is_good(version_count + 1) if version_count
|
16
|
+
end
|
17
|
+
|
18
|
+
def raise_unless_version_count_is_good(version_count)
|
19
|
+
if version_count and stored_object.versions.count > version_count
|
20
|
+
raise ConflictError, "File #{key} changed underneath you, version_count expected to be #{version_count} but was #{stored_object.versions.count}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def stored_object
|
25
|
+
@stored_object ||= s3_object(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'commander'
|
3
|
+
require 'cluster-fuck/cli'
|
4
|
+
|
5
|
+
module ClusterFuck::Commands
|
6
|
+
describe Create do
|
7
|
+
|
8
|
+
let(:amicus_env) { "test" }
|
9
|
+
let(:args) { ["test-key"] }
|
10
|
+
let(:mock_s3_object) do
|
11
|
+
mock("s3_obj",
|
12
|
+
:"exists?" => false,
|
13
|
+
:write => true
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
ClusterFuck::Commands::Edit.any_instance.stub(:run_command).and_return(true)
|
19
|
+
subject.stub(:s3_object).and_return(mock_s3_object)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should create the file" do
|
23
|
+
mock_s3_object.should_receive(:write).with("")
|
24
|
+
subject.run_command(args)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should open the editor with the file" do
|
28
|
+
ClusterFuck::Commands::Edit.any_instance.should_receive(:run_command).with(args).and_return(true)
|
29
|
+
subject.run_command(args)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should error and not write if the file already exists" do
|
33
|
+
mock_s3_object.stub(:exists?).and_return(true)
|
34
|
+
mock_s3_object.should_not_receive(:write)
|
35
|
+
->() { subject.run_command(args) }.should raise_error(ClusterFuck::S3Methods::ConflictError)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'commander'
|
3
|
+
require 'cluster-fuck/cli'
|
4
|
+
|
5
|
+
module ClusterFuck::Commands
|
6
|
+
describe Edit do
|
7
|
+
let(:key) { "test-key" }
|
8
|
+
let(:mock_writer) { mock("writer", set: true) }
|
9
|
+
|
10
|
+
let(:dummy_val) do
|
11
|
+
{
|
12
|
+
key => {
|
13
|
+
'test' => true
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:dummy_yaml) { YAML.dump(dummy_val) }
|
19
|
+
let(:mock_s3_obj) do
|
20
|
+
mock(:s3_object, {
|
21
|
+
read: dummy_yaml,
|
22
|
+
versions: mock('versions', count: 3)
|
23
|
+
})
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
ClusterFuck::Reader.any_instance.stub(:s3_object).and_return(mock_s3_obj)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "with a key" do
|
31
|
+
let(:args) { [key] }
|
32
|
+
|
33
|
+
it "should open the yaml version of the key in an editor" do
|
34
|
+
subject.should_receive(:ask_editor).with(dummy_yaml).and_return(dummy_yaml)
|
35
|
+
subject.stub(:writer).and_return(mock_writer)
|
36
|
+
mock_writer.should_receive(:set).with(dummy_val, mock_s3_obj.versions.count).once
|
37
|
+
|
38
|
+
subject.run_command(args)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should error when it is locally overriden" do
|
42
|
+
subject.reader.stub(:has_local_override?).and_return(true)
|
43
|
+
->() { subject.run_command(args) }.should raise_error(ArgumentError)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'commander'
|
3
|
+
require 'cluster-fuck/cli'
|
4
|
+
|
5
|
+
module ClusterFuck::Commands
|
6
|
+
describe Override do
|
7
|
+
let(:mock_reader) do
|
8
|
+
mock("reader",
|
9
|
+
read: "contents",
|
10
|
+
local_override_path: "/path/to/nowhere",
|
11
|
+
full_s3_path: "test/test-key"
|
12
|
+
)
|
13
|
+
end
|
14
|
+
let(:args) { ["test-key"] }
|
15
|
+
|
16
|
+
before do
|
17
|
+
ClusterFuck::Reader.should_receive(:new).with(args.first).and_return(mock_reader)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should read the remote contents and write the local contents" do
|
21
|
+
FileUtils.should_receive(:mkdir_p).with(File.dirname(mock_reader.local_override_path))
|
22
|
+
File.should_receive(:open).with(mock_reader.local_override_path, "w")
|
23
|
+
subject.run_command(args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ClusterFuck
|
4
|
+
describe Configuration do
|
5
|
+
|
6
|
+
it "should be a mash" do
|
7
|
+
Configuration.new.should be_a(Hashie::Mash)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should load from yaml" do
|
11
|
+
yaml = YAML.dump({foo: 'bar'})
|
12
|
+
Configuration.from_yaml(yaml)[:foo].should == 'bar'
|
13
|
+
end
|
14
|
+
|
15
|
+
# TODO (topper): this isn't explicitly testing the right thing
|
16
|
+
# but it does *actually* test the right thing
|
17
|
+
it "should dump to yaml" do
|
18
|
+
hsh = {'foo' => 'bar'}
|
19
|
+
Configuration.new(hsh).to_yaml.should == YAML.dump(hsh)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ClusterFuck
|
4
|
+
|
5
|
+
describe CredentialGrabber do
|
6
|
+
let(:credential_grabber) { CredentialGrabber.new }
|
7
|
+
|
8
|
+
before do
|
9
|
+
credential_grabber.stub(:exists?).and_return(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should have a singleton find" do
|
13
|
+
fake_credential_grabber = mock(:credential_grabber)
|
14
|
+
fake_credential_grabber.should_receive(:find).and_return nil
|
15
|
+
CredentialGrabber.should_receive(:new).and_return(fake_credential_grabber)
|
16
|
+
CredentialGrabber.find
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:cf_credentials) do
|
20
|
+
{
|
21
|
+
access_key_id: 'cf_access_key',
|
22
|
+
secret_access_key: 'cf_secret_key'
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:fog_credentials) do
|
27
|
+
{
|
28
|
+
default: {
|
29
|
+
aws_access_key_id: 'fog_access_key',
|
30
|
+
aws_secret_access_key: 'fog_secret_key'
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "when there is a ~/.fog file but no ~/.cluster-fuck" do
|
36
|
+
before do
|
37
|
+
File.stub(:expand_path).with(CredentialGrabber::FOG_PATH).and_return(CredentialGrabber::FOG_PATH)
|
38
|
+
credential_grabber.should_receive(:exists?).with(CredentialGrabber::FOG_PATH).and_return(true)
|
39
|
+
YAML.should_receive(:load_file).with(CredentialGrabber::FOG_PATH).and_return(fog_credentials)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return the credentials" do
|
43
|
+
credential_grabber.find.should == {
|
44
|
+
access_key_id: 'fog_access_key',
|
45
|
+
secret_access_key: 'fog_secret_key',
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "when there is a ~/.cluster-fuck file and no ~/.fog file" do
|
51
|
+
before do
|
52
|
+
credential_grabber.should_receive(:exists?).with(CredentialGrabber::CF_PATH).and_return(true)
|
53
|
+
YAML.should_receive(:load_file).with(CredentialGrabber::CF_PATH).and_return(cf_credentials)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return the credentials" do
|
57
|
+
credential_grabber.find.should == cf_credentials
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "when there is both a ~/.cluster-fuck and a ~/.fog file" do
|
62
|
+
before do
|
63
|
+
credential_grabber.should_receive(:exists?).and_return(true)
|
64
|
+
YAML.should_receive(:load_file).with(CredentialGrabber::CF_PATH).and_return(cf_credentials)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should return the cluster-fuck credentials" do
|
68
|
+
credential_grabber.find.should == cf_credentials
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "when there are no credential files" do
|
73
|
+
|
74
|
+
it "should return nil" do
|
75
|
+
credential_grabber.find.should be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ClusterFuck
|
4
|
+
|
5
|
+
describe Reader do
|
6
|
+
let(:amicus_env) { 'test' }
|
7
|
+
let(:key) { "test-key" }
|
8
|
+
let(:reader) { Reader.new(key) }
|
9
|
+
let(:mock_s3_obj) { mock(:s3_object, read: nil) }
|
10
|
+
|
11
|
+
|
12
|
+
it "should set amicus_env" do
|
13
|
+
reader.amicus_env.should == amicus_env
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "when there is a local override" do
|
17
|
+
let(:local_yaml) { YAML.dump(foo: true) }
|
18
|
+
let(:local_path) { "cluster-fuck/#{amicus_env}/#{key}" }
|
19
|
+
|
20
|
+
before do
|
21
|
+
File.should_receive(:exists?).at_least(1).times.with(local_path).and_return(true)
|
22
|
+
File.stub(:read).with(local_path).and_return(local_yaml)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should #has_local_override?" do
|
26
|
+
reader.has_local_override?.should be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should use the local override when it exists" do
|
30
|
+
reader.read.should == Configuration.from_yaml(local_yaml)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#read" do
|
35
|
+
let(:version_count) { 3 }
|
36
|
+
|
37
|
+
before do
|
38
|
+
mock_s3_obj.stub({
|
39
|
+
read: YAML.dump({
|
40
|
+
foo: 'bar'
|
41
|
+
}),
|
42
|
+
versions: mock('versions', count: version_count)
|
43
|
+
})
|
44
|
+
reader.stub(:s3_object).with(key).and_return(mock_s3_obj)
|
45
|
+
reader.stub(:s3_object).with(key.to_sym).and_return(mock_s3_obj)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should load the remote file and yaml parse it" do
|
49
|
+
reader.read['foo'].should == 'bar'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should set the version count" do
|
53
|
+
reader.read
|
54
|
+
reader.version_count.should == version_count
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ClusterFuck
|
4
|
+
class DummyClass
|
5
|
+
include S3Methods
|
6
|
+
end
|
7
|
+
|
8
|
+
describe S3Methods do
|
9
|
+
let(:amicus_env) { 'test' }
|
10
|
+
let(:config_bucket) { ClusterFuck::CONFIG_BUCKET }
|
11
|
+
let(:key) { 'test_key' }
|
12
|
+
let(:dummy_instance) { DummyClass.new }
|
13
|
+
|
14
|
+
let(:mock_listing) { mock('obj', key: "#{amicus_env}/#{key}") }
|
15
|
+
let(:bucket_objects) { mock('bucket_objects', with_prefix: [mock_listing]) }
|
16
|
+
let(:mock_bucket) { mock(:bucket, objects: bucket_objects) }
|
17
|
+
|
18
|
+
before do
|
19
|
+
DummyClass.any_instance.stub(:bucket).and_return(mock_bucket)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should qualify the keys it gets with the namespace" do
|
23
|
+
mock_bucket.stub(:objects) { { key => 'fail',
|
24
|
+
"#{amicus_env}/#{key}" => 'pass'} }
|
25
|
+
dummy_instance.stub(:bucket) { mock_bucket }
|
26
|
+
dummy_instance.s3_object(key).should == 'pass'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should enumerate all files with the prefix" do
|
30
|
+
bucket_objects.should_receive(:with_prefix).with(amicus_env).and_return([mock_listing])
|
31
|
+
dummy_instance.all_files.should == [mock_listing.key]
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ClusterFuck
|
4
|
+
|
5
|
+
describe Writer do
|
6
|
+
let(:amicus_env) { 'development' }
|
7
|
+
let(:key) { 'tester' }
|
8
|
+
let(:reader) { Reader.new(key) }
|
9
|
+
let(:mock_s3_obj) { mock(:s3_object, read: nil) }
|
10
|
+
let(:writer) { Writer.new(key) }
|
11
|
+
let(:initial_version_count) { 3 }
|
12
|
+
|
13
|
+
before do
|
14
|
+
mock_s3_obj.stub(
|
15
|
+
read: YAML.dump({
|
16
|
+
foo: 'bar',
|
17
|
+
deep: {
|
18
|
+
works: 'too',
|
19
|
+
even: 'here'
|
20
|
+
}
|
21
|
+
}),
|
22
|
+
versions: mock('versions', count: initial_version_count)
|
23
|
+
)
|
24
|
+
Reader.any_instance.stub(:s3_object).with("#{key}").and_return(mock_s3_obj)
|
25
|
+
writer.stub(:s3_object).and_return(mock_s3_obj)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#set" do
|
29
|
+
it "should write the configuration to the file" do
|
30
|
+
writer.should_receive(:s3_object).with(key).and_return(mock_s3_obj)
|
31
|
+
|
32
|
+
mock_s3_obj.should_receive(:write).with(YAML.dump({
|
33
|
+
'deep' => {
|
34
|
+
'even' => 'changed'
|
35
|
+
}
|
36
|
+
}))
|
37
|
+
|
38
|
+
do_write
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "with version counts" do
|
42
|
+
|
43
|
+
it "should work normally when nothing has changed underneath" do
|
44
|
+
mock_write
|
45
|
+
mock_s3_obj.versions.stub(:count).and_return(initial_version_count, (initial_version_count + 1))
|
46
|
+
|
47
|
+
do_write
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should raise an error and not write when it has changed before the set" do
|
51
|
+
mock_s3_obj.should_not_receive(:write)
|
52
|
+
mock_s3_obj.versions.stub(:count).and_return(initial_version_count + 1)
|
53
|
+
|
54
|
+
->() { do_write }.should raise_error(ClusterFuck::S3Methods::ConflictError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should raise an error if the version count has hopped AFTER our write" do
|
58
|
+
mock_write
|
59
|
+
mock_s3_obj.versions.stub(:count).and_return(initial_version_count, (initial_version_count + 2))
|
60
|
+
|
61
|
+
->() { do_write }.should raise_error(ClusterFuck::S3Methods::ConflictError)
|
62
|
+
end
|
63
|
+
|
64
|
+
def mock_write
|
65
|
+
mock_s3_obj.should_receive(:write).with(YAML.dump({
|
66
|
+
'deep' => {
|
67
|
+
'even' => 'changed'
|
68
|
+
}
|
69
|
+
}))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def do_write
|
74
|
+
writer.set(Configuration.new({
|
75
|
+
deep: {
|
76
|
+
even: 'changed'
|
77
|
+
}
|
78
|
+
}), initial_version_count)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
require 'pry'
|
3
|
+
require 'cluster-fuck'
|
4
|
+
|
5
|
+
Dir["spec/support/**/*.rb"].each {|f| require File.expand_path(f)}
|
6
|
+
|
7
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
8
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
9
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
10
|
+
# loaded once.
|
11
|
+
#
|
12
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
config.filter_run :focus
|
17
|
+
|
18
|
+
# Run specs in random order to surface order dependencies. If you find an
|
19
|
+
# order dependency and want to debug it, you can fix the order by providing
|
20
|
+
# the seed, which is printed after each run.
|
21
|
+
# --seed 1234
|
22
|
+
config.order = 'random'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cluster-fuck
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Topper Bowers
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: hashie
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: commander
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 4.1.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 4.1.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: cluster-aware S3 and ZK based config getter/setter
|
79
|
+
email:
|
80
|
+
- topper@amicushq.com
|
81
|
+
executables:
|
82
|
+
- clusterfuck
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- .rspec
|
88
|
+
- .rvmrc
|
89
|
+
- Gemfile
|
90
|
+
- Guardfile
|
91
|
+
- LICENSE.txt
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- bin/clusterfuck
|
95
|
+
- cluster-fuck.gemspec
|
96
|
+
- lib/cluster-fuck.rb
|
97
|
+
- lib/cluster-fuck/cli.rb
|
98
|
+
- lib/cluster-fuck/commands/create.rb
|
99
|
+
- lib/cluster-fuck/commands/edit.rb
|
100
|
+
- lib/cluster-fuck/commands/list.rb
|
101
|
+
- lib/cluster-fuck/commands/override.rb
|
102
|
+
- lib/cluster-fuck/configuration.rb
|
103
|
+
- lib/cluster-fuck/credential_grabber.rb
|
104
|
+
- lib/cluster-fuck/reader.rb
|
105
|
+
- lib/cluster-fuck/s3_methods.rb
|
106
|
+
- lib/cluster-fuck/version.rb
|
107
|
+
- lib/cluster-fuck/writer.rb
|
108
|
+
- spec/lib/cluster-fuck/commands/create_spec.rb
|
109
|
+
- spec/lib/cluster-fuck/commands/edit_spec.rb
|
110
|
+
- spec/lib/cluster-fuck/commands/override_spec.rb
|
111
|
+
- spec/lib/cluster-fuck/configuration_spec.rb
|
112
|
+
- spec/lib/cluster-fuck/credential_grabber_spec.rb
|
113
|
+
- spec/lib/cluster-fuck/reader_spec.rb
|
114
|
+
- spec/lib/cluster-fuck/s3_methods_spec.rb
|
115
|
+
- spec/lib/cluster-fuck/writer_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- spec/support/amicus_env_setter.rb
|
118
|
+
homepage: ''
|
119
|
+
licenses: []
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 1.8.24
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: ''
|
142
|
+
test_files:
|
143
|
+
- spec/lib/cluster-fuck/commands/create_spec.rb
|
144
|
+
- spec/lib/cluster-fuck/commands/edit_spec.rb
|
145
|
+
- spec/lib/cluster-fuck/commands/override_spec.rb
|
146
|
+
- spec/lib/cluster-fuck/configuration_spec.rb
|
147
|
+
- spec/lib/cluster-fuck/credential_grabber_spec.rb
|
148
|
+
- spec/lib/cluster-fuck/reader_spec.rb
|
149
|
+
- spec/lib/cluster-fuck/s3_methods_spec.rb
|
150
|
+
- spec/lib/cluster-fuck/writer_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/amicus_env_setter.rb
|