cloudspec 0.0.2
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 +15 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.rubocop.yml +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/Gemfile +5 -0
- data/LICENSE.md +24 -0
- data/README.md +30 -0
- data/Rakefile +52 -0
- data/bin/cloudspec +9 -0
- data/cloudspec.gemspec +33 -0
- data/config/template.yml +9 -0
- data/features/instances.feature +36 -0
- data/features/support/env.rb +2 -0
- data/lib/cloudspec/amzn/base.rb +67 -0
- data/lib/cloudspec/amzn/buckets.rb +0 -0
- data/lib/cloudspec/amzn/instances.rb +25 -0
- data/lib/cloudspec/amzn/security_groups.rb +25 -0
- data/lib/cloudspec/amzn/subnets.rb +0 -0
- data/lib/cloudspec/cli.rb +83 -0
- data/lib/cloudspec/version.rb +3 -0
- data/lib/cloudspec.rb +34 -0
- data/rules/amzn/buckets.rb +7 -0
- data/rules/amzn/instances.rb +52 -0
- data/rules/amzn/security_groups.rb +24 -0
- data/rules/amzn/subnets.rb +7 -0
- data/spec/cloudspec_spec.rb +3 -0
- data/spec/lib/amzn/base_spec.rb +75 -0
- data/spec/lib/amzn/instances_spec.rb +51 -0
- data/spec/lib/cli_spec.rb +0 -0
- data/spec/spec_helper.rb +34 -0
- metadata +238 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YWE4NGM4MzQ1OGIzMTA4MWMyMWMwMzMxYmJlOTFmM2RjNzMzNDBiYQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NWFlMThjZjAxMDExOTNjZDkyNTEyZTQwODM0ZjM4ZTE2NGJlNjFkNQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
M2JmNzdlN2ZhNWE5NmJmNDQxMTgyMjVmMmQyZjQ1MDA1N2EwZDU4NWZjMjNi
|
10
|
+
MmRhZTBlMjE5NWZhZTJiMjk3ZmU4ODAxOTAzNDljMzNlYzFiMTUxMjRhYjE1
|
11
|
+
M2IzMjQ3NmFkM2RlMmM5YzZhNzVlYjY5MmM1MWU4ODZkZWFkZDk=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YzE4MzQxZmUwOGZhYzVhNDJmMGM4NDg3OTc4ODFmZTk3Y2UwNjRjOGUyMWQx
|
14
|
+
MmQ1MTZhNmYxNmJhNTk2MjM0ZjkwZTdkNmQ4MzYwOTc3ZDgzYzlhZjQ0NWM2
|
15
|
+
OWM0MTM5YTBiNThiNzNmMmVhNDVjYTQ2YTY2ZGY1ZWU2MzE2Yjk=
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
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
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.DS_Store
|
24
|
+
*.swp
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
cloudspec
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.4
|
data/.travis.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
rvm:
|
4
|
+
- 2.2.4
|
5
|
+
script: bundle exec rake ci:build
|
6
|
+
deploy:
|
7
|
+
provider: rubygems
|
8
|
+
api_key:
|
9
|
+
secure: d0cXjiVXKoRIpjKkXOGpVmil1XZkoZHRWQ+mpLwBYwxsyXppHjO/9qVp1Br1qocXRN/IFTc/Q27Hq5PDLLy0UkVKUixXQ+Qbq6GhByHBuC2Y6gxO66ChrntxX4cbw3rHc42XR/LORYB4jjIPxZY5lQ6WjNGa08i6G3LeMfmMfCM=
|
10
|
+
on:
|
11
|
+
tags: true
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
CloudSpec is distributed under the following license:
|
2
|
+
----------------------------------------------------
|
3
|
+
|
4
|
+
Copyright (c) 2014 Lookout, Inc.
|
5
|
+
https://www.lookout.com/about/contact
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# CloudSpec
|
2
|
+
[](https://travis-ci.org/AngryEgret/cloudspec)
|
3
|
+
|
4
|
+
TODO: Write a gem description
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'cloudspec'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install cloudspec
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
TODO: Write usage instructions here
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
|
26
|
+
1. Fork it ( https://github.com/[my-github-username]/cloudspec/fork )
|
27
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
28
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
29
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
30
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'cucumber/rake/task'
|
6
|
+
require 'cucumber'
|
7
|
+
|
8
|
+
desc 'Ensures we keep up 100% YARD coverage'
|
9
|
+
task :yard_coverage do
|
10
|
+
coverage_stats = `yard stats --list-undoc 2>&1`
|
11
|
+
puts coverage_stats
|
12
|
+
|
13
|
+
yard_regexp = /^\s*(.*)% documented/
|
14
|
+
percent = coverage_stats.scan(yard_regexp).first.first.to_f
|
15
|
+
minimum_coverage = 0.0
|
16
|
+
|
17
|
+
if percent < minimum_coverage
|
18
|
+
fail 'Documentation coverage is less than #{minimum_coverage}%'
|
19
|
+
else
|
20
|
+
puts "\nNice work! Documentation coverage above #{minimum_coverage}%!"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Checks the spec coverage and fails if it is less than 100%'
|
25
|
+
task :check_code_coverage do
|
26
|
+
percent = File.read('./coverage/coverage_percent.txt').to_f
|
27
|
+
minimum_coverage = 0.0
|
28
|
+
if percent < minimum_coverage
|
29
|
+
abort "Spec coverage was not high enough: #{percent.round(2)}% < #{minimum_coverage}%"
|
30
|
+
else
|
31
|
+
puts "Nice job! Spec coverage is still above #{minimum_coverage}%"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
36
|
+
t.cucumber_opts = 'features --format progress'
|
37
|
+
end
|
38
|
+
|
39
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
40
|
+
task.fail_on_error = false
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :ci do
|
44
|
+
desc 'Sets things up for a ci build'
|
45
|
+
|
46
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
47
|
+
t.verbose = true
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'Run a ci build'
|
51
|
+
task build: [:spec, :features, :yard_coverage, :check_code_coverage, :rubocop]
|
52
|
+
end
|
data/bin/cloudspec
ADDED
data/cloudspec.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cloudspec/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'cloudspec'
|
8
|
+
spec.version = CloudSpec::VERSION
|
9
|
+
spec.authors = ['Ryan Greget']
|
10
|
+
spec.email = ['rgreget@gmail.com']
|
11
|
+
spec.summary = %q{Simple tool to harvest dead weight in AWS}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = 'https://github.com/AngryEgret/cloudspec'
|
14
|
+
spec.license = ''
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
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 'thor', '~> 0.19'
|
22
|
+
spec.add_dependency 'fog', '~> 1.37'
|
23
|
+
spec.add_dependency 'rspec-expectations', '~> 3.4'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'aruba'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'simplecov'
|
31
|
+
spec.add_development_dependency 'yard'
|
32
|
+
spec.add_development_dependency 'rubocop'
|
33
|
+
end
|
data/config/template.yml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
Feature: Harvest instances
|
2
|
+
In order to harvest deviant instances from the command line
|
3
|
+
A user should provide the incidents command and path to the creditials
|
4
|
+
|
5
|
+
Scenario: Correctly run the instances command
|
6
|
+
Given I run `cloudspec init --path=/tmp`
|
7
|
+
When I run `cloudspec amazon instances --yaml=/tmp/config/template.yml --rules=/tmp/rules --mock`
|
8
|
+
Then the exit status should be 0
|
9
|
+
And the output should contain "INFO -- : Beginning instance harvest ..."
|
10
|
+
And the output should contain "INFO -- : Instance harvest complete."
|
11
|
+
|
12
|
+
Given I run `cloudspec init --path=/tmp`
|
13
|
+
When I run `cloudspec amazon instances -y /tmp/config/template.yml -r /tmp/rules --mock`
|
14
|
+
Then the exit status should be 0
|
15
|
+
And the output should contain "INFO -- : Beginning instance harvest ..."
|
16
|
+
And the output should contain "INFO -- : Instance harvest complete."
|
17
|
+
|
18
|
+
Scenario: Run the instances command with no parameters
|
19
|
+
Given I run `cloudspec init --path=/tmp`
|
20
|
+
When I run `cloudspec amazon instances --mock`
|
21
|
+
Then the exit status should be 0
|
22
|
+
And the output should contain "No value provided for required options"
|
23
|
+
And the output should contain "--yaml"
|
24
|
+
And the output should contain "--rules"
|
25
|
+
|
26
|
+
Scenario: Run the instances command with a missing credentials file
|
27
|
+
Given I run `cloudspec init --path=/tmp`
|
28
|
+
When I run `cloudspec amazon instances --yaml=/fail --rules=/tmp/rules --mock`
|
29
|
+
Then the exit status should be 1
|
30
|
+
And the output should contain "Could not find YAML config file '/fail' (CloudSpec::FileNotFoundError)"
|
31
|
+
|
32
|
+
Scenario: Run the instances command with a missing rules path
|
33
|
+
Given I run `cloudspec init --path=/tmp`
|
34
|
+
When I run `cloudspec amazon instances --yaml=/tmp/config/template.yml --rules=/fail --mock`
|
35
|
+
Then the exit status should be 1
|
36
|
+
And the output should contain "Could not find rules path '/fail' (CloudSpec::FileNotFoundError)"
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'rspec/expectations'
|
3
|
+
|
4
|
+
module CloudSpec
|
5
|
+
module AMZN
|
6
|
+
class Base
|
7
|
+
include RSpec::Matchers
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
mock?
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def harvest
|
15
|
+
load_rules
|
16
|
+
self.class.include_rules
|
17
|
+
|
18
|
+
CloudSpec.log.warn 'harvesting ...'
|
19
|
+
|
20
|
+
accounts = CloudSpec.config['aws']
|
21
|
+
|
22
|
+
accounts.each do |account_name, credentials|
|
23
|
+
process_account(account_name, credentials)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_rules
|
28
|
+
CloudSpec.log.debug 'loading rules ...'
|
29
|
+
|
30
|
+
rules_path = File.expand_path(CloudSpec.options[:rules])
|
31
|
+
Dir["#{rules_path}/amzn/**/*.rb"].each do |file|
|
32
|
+
require file
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_account(account_name, credentials = { 'access_key' => nil, 'secret_key' => nil })
|
37
|
+
CloudSpec.log.debug "processing account #{account_name} ..."
|
38
|
+
regions(credentials).each do |region|
|
39
|
+
objects(credentials, region).each do |object|
|
40
|
+
evaluate_object(account_name, region, object)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def regions(credentials)
|
46
|
+
CloudSpec.log.debug 'getting regions ...'
|
47
|
+
aws_client = compute_client(credentials)
|
48
|
+
aws_client.describe_regions.body['regionInfo'].map { |region| region['regionName'] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def compute_client(credentials = { 'access_key' => '', 'secret_key' => '' }, region = nil)
|
52
|
+
CloudSpec.log.debug 'creating compute client ...'
|
53
|
+
Fog::Compute.new(
|
54
|
+
provider: 'AWS',
|
55
|
+
aws_access_key_id: credentials['access_key'],
|
56
|
+
aws_secret_access_key: credentials['secret_key'],
|
57
|
+
region: region
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def mock?
|
62
|
+
CloudSpec.log.debug 'Checking Mocking settings ...'
|
63
|
+
Fog.mock! if CloudSpec.options[:mock]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CloudSpec
|
2
|
+
module AMZN
|
3
|
+
class Instances < Base
|
4
|
+
def self.include_rules
|
5
|
+
CloudSpec.log.debug 'including rules ...'
|
6
|
+
include ::AMZN::InstanceRules
|
7
|
+
end
|
8
|
+
|
9
|
+
def objects(credentials, region)
|
10
|
+
CloudSpec.log.debug 'getting instances ...'
|
11
|
+
aws_client = compute_client(credentials, region)
|
12
|
+
aws_client.servers
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate_object(account_name, region, object)
|
16
|
+
CloudSpec.log.debug "Evaluating object #{object.id} ..."
|
17
|
+
begin
|
18
|
+
evaluate(object)
|
19
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
20
|
+
CloudSpec.log.error "[#{account_name}][#{region}][#{object.id}] - " + e.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CloudSpec
|
2
|
+
module AMZN
|
3
|
+
class SecurityGroups < Base
|
4
|
+
def self.include_rules
|
5
|
+
CloudSpec.log.debug 'including rules ...'
|
6
|
+
include AMZN::SecurityGroupRules
|
7
|
+
end
|
8
|
+
|
9
|
+
def objects(credentials, region)
|
10
|
+
CloudSpec.log.debug 'getting groups ...'
|
11
|
+
aws_client = compute_client(credentials, region)
|
12
|
+
aws_client.security_groups
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate_object(account_name, region, object)
|
16
|
+
CloudSpec.log.debug "Evaluating object #{object.group_id} ..."
|
17
|
+
begin
|
18
|
+
evaluate(object)
|
19
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
20
|
+
CloudSpec.log.error "[#{account_name}][#{region}][#{object.group_id}] - " + e.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
File without changes
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module CloudSpec
|
4
|
+
class AMZN_CLI < Thor
|
5
|
+
namespace :amazon
|
6
|
+
|
7
|
+
def self.shared_options
|
8
|
+
method_option :mock, type: :boolean, desc: 'Mock all cloud calls', aliases: '-m'
|
9
|
+
method_option :dry_run, type: :boolean, desc: 'Non-destructive run', aliases: '-d'
|
10
|
+
method_option :yaml, type: :string, required: true, aliases: '-y', desc: 'The path to the clould credentials yaml file'
|
11
|
+
method_option :rules, type: :string, required: true, aliases: '-r', desc: 'The path to the rules'
|
12
|
+
end
|
13
|
+
|
14
|
+
no_tasks do
|
15
|
+
def validate
|
16
|
+
CloudSpec.options(options)
|
17
|
+
|
18
|
+
CloudSpec.options[:verbose] ? CloudSpec.log.level = Logger::DEBUG : CloudSpec.log.level = Logger::INFO
|
19
|
+
|
20
|
+
fail CloudSpec::FileNotFoundError, "Could not find YAML config file '#{CloudSpec.options[:yaml]}'" unless File.exist? CloudSpec.options[:yaml]
|
21
|
+
fail CloudSpec::FileNotFoundError, "Could not find rules path '#{CloudSpec.options[:rules]}'" unless Dir.exist? CloudSpec.options[:rules]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'all', 'Harvest all AWS objects'
|
26
|
+
shared_options
|
27
|
+
def all
|
28
|
+
validate
|
29
|
+
|
30
|
+
invoke :instances
|
31
|
+
invoke :groups
|
32
|
+
rescue => e
|
33
|
+
CloudSpec.log.error e.to_s
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'instances', 'Harvest AWS Instances'
|
38
|
+
shared_options
|
39
|
+
def instances
|
40
|
+
validate
|
41
|
+
|
42
|
+
instances = CloudSpec::AMZN::Instances.new
|
43
|
+
CloudSpec.log.info 'Beginning instance harvest ...'
|
44
|
+
instances.harvest
|
45
|
+
CloudSpec.log.info 'Instance harvest complete.'
|
46
|
+
rescue => e
|
47
|
+
CloudSpec.log.error e.to_s
|
48
|
+
exit 1
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'groups', 'Harvest AWS Security Groups'
|
52
|
+
shared_options
|
53
|
+
def groups
|
54
|
+
validate
|
55
|
+
|
56
|
+
groups = CloudSpec::AMZN::SecurityGroups.new
|
57
|
+
CloudSpec.log.info 'Beginning group harvest ...'
|
58
|
+
groups.harvest
|
59
|
+
CloudSpec.log.info 'Group harvest complete.'
|
60
|
+
rescue => e
|
61
|
+
CloudSpec.log.error e.to_s
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class CLI < Thor
|
67
|
+
class_option :verbose, type: :boolean, desc: 'Enable verbose logging', aliases: '-v'
|
68
|
+
|
69
|
+
desc 'init', 'Initialize a rules directory and credentials file'
|
70
|
+
method_option :path, type: :string, required: true, aliases: '-p', desc: 'The path to create scaffolding within'
|
71
|
+
def init
|
72
|
+
fail CloudSpec::FileNotFoundError, "Could not find initialization path '#{options[:path]}'" unless Dir.exist? options[:path]
|
73
|
+
|
74
|
+
CloudSpec.build_scaffold(options[:path])
|
75
|
+
rescue => e
|
76
|
+
CloudSpec.log.error e.to_s
|
77
|
+
exit 1
|
78
|
+
end
|
79
|
+
|
80
|
+
desc 'amazon SUBCOMMAND ...ARGS', 'Evaluate Amazon Cloud Objects'
|
81
|
+
subcommand 'amazon', CloudSpec::AMZN_CLI
|
82
|
+
end
|
83
|
+
end
|
data/lib/cloudspec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'cloudspec/version'
|
5
|
+
|
6
|
+
require 'cloudspec/amzn/base'
|
7
|
+
require 'cloudspec/amzn/instances'
|
8
|
+
require 'cloudspec/amzn/security_groups'
|
9
|
+
|
10
|
+
module CloudSpec
|
11
|
+
InvalidCLIOptionsError = Class.new(Exception)
|
12
|
+
FileNotFoundError = Class.new(Exception)
|
13
|
+
|
14
|
+
def self.log
|
15
|
+
@logger ||= Logger.new(STDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.config
|
19
|
+
@config ||= YAML.load_file(CloudSpec.options[:yaml])
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.options(options = nil)
|
23
|
+
@options ||= options
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.root
|
27
|
+
File.expand_path '../..', __FILE__
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.build_scaffold(path)
|
31
|
+
FileUtils.cp_r File.join(root, './config'), path
|
32
|
+
FileUtils.cp_r File.join(root, './rules'), path
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module AMZN
|
2
|
+
module InstanceRules
|
3
|
+
# <Fog::Compute::AWS::Server
|
4
|
+
# id="i-12345678",
|
5
|
+
# ami_launch_index=0,
|
6
|
+
# associate_public_ip=nil,
|
7
|
+
# availability_zone="us-east-1a",
|
8
|
+
# block_device_mapping=[{"deviceName"=>"/dev/sda1", "volumeId"=>"vol-12345678", "status"=>"attached", "attachTime"=>2014-07-10 21:44:24 UTC, "deleteOnTermination"=>false}],
|
9
|
+
# network_interfaces=[],
|
10
|
+
# client_token="ckgps1234567890123",
|
11
|
+
# dns_name="ec2-11-22-33-44.compute-1.amazonaws.com",
|
12
|
+
# ebs_optimized=false,
|
13
|
+
# groups=["default", "other"],
|
14
|
+
# flavor_id="m4.large",
|
15
|
+
# hypervisor="xen",
|
16
|
+
# iam_instance_profile={},
|
17
|
+
# image_id="ami-abcd1234",
|
18
|
+
# kernel_id="aki-abcd1234",
|
19
|
+
# key_name="default",
|
20
|
+
# created_at=2014-07-10 21:44:40 UTC,
|
21
|
+
# lifecycle=nil,
|
22
|
+
# monitoring=false,
|
23
|
+
# placement_group=nil,
|
24
|
+
# platform=nil,
|
25
|
+
# product_codes=[],
|
26
|
+
# private_dns_name="ip-11-22-33-44.ec2.internal",
|
27
|
+
# private_ip_address="11.22.33.44",
|
28
|
+
# public_ip_address="22.33.44.55",
|
29
|
+
# ramdisk_id=nil,
|
30
|
+
# reason=nil,
|
31
|
+
# requester_id=nil,
|
32
|
+
# root_device_name=nil,
|
33
|
+
# root_device_type="ebs",
|
34
|
+
# security_group_ids=["sg-abcd1234", "sg-1234abcd"],
|
35
|
+
# source_dest_check=nil,
|
36
|
+
# spot_instance_request_id=nil,
|
37
|
+
# state="running",
|
38
|
+
# state_reason={},
|
39
|
+
# subnet_id=nil,
|
40
|
+
# tenancy="default",
|
41
|
+
# tags={"Name"=>"default"},
|
42
|
+
# user_data=nil,
|
43
|
+
# virtualization_type="paravirtual",
|
44
|
+
# vpc_id=nil
|
45
|
+
# >
|
46
|
+
def evaluate(instance)
|
47
|
+
expect(instance.tags).to have_key('Name'), 'expected instance.tags to have key "Name"'
|
48
|
+
expect(instance.flavor_id).to eq('m3.large'), "expected instance.flavor_id to equal 'm3.large', got #{instance.flavor_id}"
|
49
|
+
expect(instance.vpc_id).to_not be_nil, 'expected instance to belong to VPC'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AMZN
|
2
|
+
module SecurityGroupRules
|
3
|
+
# <Fog::Compute::AWS::SecurityGroup
|
4
|
+
# name="default",
|
5
|
+
# description="default security group",
|
6
|
+
# group_id="sg-12345678",
|
7
|
+
# ip_permissions=[{"groups"=>[], "ipRanges"=>[{"cidrIp"=>"0.0.0.0/0"}], "ipProtocol"=>"tcp", "fromPort"=>22, "toPort"=>22}],
|
8
|
+
# ip_permissions_egress=[],
|
9
|
+
# owner_id="123456789012",
|
10
|
+
# vpc_id=nil,
|
11
|
+
# tags={}
|
12
|
+
# >
|
13
|
+
def evaluate(group)
|
14
|
+
group.ip_permissions.each do |ip_permission|
|
15
|
+
ip_permission['ipRanges'].each do |range|
|
16
|
+
if range.value? '0.0.0.0/0'
|
17
|
+
expect(ip_permission['fromPort']).to eq(80).or eq(443)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
expect(group.vpc_id).to_not be_nil, 'expected group to belong to VPC'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
describe CloudSpec::AMZN::Base do
|
2
|
+
let(:base) { CloudSpec::AMZN::Base.new }
|
3
|
+
let(:credentials) { { 'access_key' => '', 'secret_key' => '' } }
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
allow(CloudSpec).to receive_message_chain(:log, :debug)
|
7
|
+
allow(CloudSpec).to receive(:options) {
|
8
|
+
{
|
9
|
+
mock: true,
|
10
|
+
verbose: false,
|
11
|
+
rules: './rules'
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.initialize' do
|
17
|
+
it 'should call the mock? method' do
|
18
|
+
expect_any_instance_of(CloudSpec::AMZN::Base).to receive(:mock?)
|
19
|
+
|
20
|
+
CloudSpec::AMZN::Base.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.process_account' do
|
25
|
+
it 'should not raise error' do
|
26
|
+
expect_any_instance_of(CloudSpec::AMZN::Base).to receive(:objects).twice.and_return([])
|
27
|
+
expect { base.process_account('dev', credentials) }.to_not raise_error
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '.load_rules' do
|
32
|
+
it 'should not raise error' do
|
33
|
+
expect { base.load_rules }.to_not raise_error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '.regions' do
|
38
|
+
it 'should return an array of regions' do
|
39
|
+
expect(base.regions(credentials)).to eq ['eu-west-1', 'us-east-1']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '.compute_client' do
|
44
|
+
it 'should return a Fog::Compute object' do
|
45
|
+
expect(base.compute_client(credentials)).to be_an_instance_of(Fog::Compute::AWS::Mock)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should be able to consume a region argument' do
|
49
|
+
expect(base.compute_client(credentials, 'eu-west-1').region).to eq('eu-west-1')
|
50
|
+
expect(base.compute_client(credentials, 'us-east-1').region).to eq('us-east-1')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '.mock?' do
|
55
|
+
it 'should call Fog.mock! if the mock option is set' do
|
56
|
+
expect(Fog).to receive(:mock!).at_least(:twice)
|
57
|
+
|
58
|
+
base.mock?
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should not call Fog.mock! if the mock option is unset' do
|
62
|
+
allow(CloudSpec).to receive(:options) {
|
63
|
+
{
|
64
|
+
mock: false,
|
65
|
+
verbose: false,
|
66
|
+
rules: './rules'
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
expect(Fog).to_not receive(:mock!)
|
71
|
+
|
72
|
+
base.mock?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
describe CloudSpec::AMZN::Instances do
|
2
|
+
let(:instances) { CloudSpec::AMZN::Instances.new }
|
3
|
+
let(:credentials) { { 'access_key' => '', 'secret_key' => '' } }
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
allow(CloudSpec).to receive_message_chain(:log, :debug)
|
7
|
+
allow(CloudSpec).to receive_message_chain(:log, :warn)
|
8
|
+
allow(CloudSpec).to receive(:options) {
|
9
|
+
{
|
10
|
+
mock: true,
|
11
|
+
dry_run: true,
|
12
|
+
yaml: 'config/template.yml',
|
13
|
+
rules: './rules/',
|
14
|
+
verbose: false
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.harvest' do
|
20
|
+
it 'should load the rules' do
|
21
|
+
expect(instances).to receive(:load_rules)
|
22
|
+
expect(CloudSpec::AMZN::Instances).to receive(:include_rules)
|
23
|
+
expect(instances).to receive(:process_account).at_least(:once)
|
24
|
+
|
25
|
+
instances.harvest
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '.include_rules' do
|
30
|
+
it 'should implement the evaluate method from InstanceRules' do
|
31
|
+
instances.load_rules
|
32
|
+
CloudSpec::AMZN::Instances.include_rules
|
33
|
+
|
34
|
+
expect(instances).to respond_to :evaluate
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.evaluate_instance' do
|
39
|
+
pending
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.instances' do
|
43
|
+
it 'should return an Array of instances' do
|
44
|
+
expect(instances.objects(credentials, 'us-east-1')).to be_an_instance_of(Fog::Compute::AWS::Servers)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.process_account' do
|
49
|
+
pending
|
50
|
+
end
|
51
|
+
end
|
File without changes
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause this
|
4
|
+
# file to always be loaded, without a need to explicitly require it in any files.
|
5
|
+
#
|
6
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
7
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
8
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
9
|
+
# individual file that may not need all of that loaded. Instead, make a
|
10
|
+
# separate helper file that requires this one and then use it only in the specs
|
11
|
+
# that actually need it.
|
12
|
+
#
|
13
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
14
|
+
# users commonly want.
|
15
|
+
#
|
16
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
17
|
+
|
18
|
+
require 'simplecov'
|
19
|
+
require 'cloudspec'
|
20
|
+
|
21
|
+
Fog.mock!
|
22
|
+
|
23
|
+
SimpleCov.start
|
24
|
+
|
25
|
+
SimpleCov.at_exit do
|
26
|
+
File.open(File.join(SimpleCov.coverage_path, 'coverage_percent.txt'), 'w') do |f|
|
27
|
+
f.write SimpleCov.result.covered_percent
|
28
|
+
end
|
29
|
+
SimpleCov.result.format!
|
30
|
+
end
|
31
|
+
|
32
|
+
RSpec.configure do |config|
|
33
|
+
config.order = :random
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Greget
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.19'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.19'
|
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.37'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.37'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-expectations
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
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.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.11'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: aruba
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
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: yard
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ! '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ! '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ! '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ! '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: Simple tool to harvest dead weight in AWS
|
168
|
+
email:
|
169
|
+
- rgreget@gmail.com
|
170
|
+
executables:
|
171
|
+
- cloudspec
|
172
|
+
extensions: []
|
173
|
+
extra_rdoc_files: []
|
174
|
+
files:
|
175
|
+
- .gitignore
|
176
|
+
- .rspec
|
177
|
+
- .rubocop.yml
|
178
|
+
- .ruby-gemset
|
179
|
+
- .ruby-version
|
180
|
+
- .travis.yml
|
181
|
+
- Gemfile
|
182
|
+
- LICENSE.md
|
183
|
+
- README.md
|
184
|
+
- Rakefile
|
185
|
+
- bin/cloudspec
|
186
|
+
- cloudspec.gemspec
|
187
|
+
- config/template.yml
|
188
|
+
- features/instances.feature
|
189
|
+
- features/support/env.rb
|
190
|
+
- lib/cloudspec.rb
|
191
|
+
- lib/cloudspec/amzn/base.rb
|
192
|
+
- lib/cloudspec/amzn/buckets.rb
|
193
|
+
- lib/cloudspec/amzn/instances.rb
|
194
|
+
- lib/cloudspec/amzn/security_groups.rb
|
195
|
+
- lib/cloudspec/amzn/subnets.rb
|
196
|
+
- lib/cloudspec/cli.rb
|
197
|
+
- lib/cloudspec/version.rb
|
198
|
+
- rules/amzn/buckets.rb
|
199
|
+
- rules/amzn/instances.rb
|
200
|
+
- rules/amzn/security_groups.rb
|
201
|
+
- rules/amzn/subnets.rb
|
202
|
+
- spec/cloudspec_spec.rb
|
203
|
+
- spec/lib/amzn/base_spec.rb
|
204
|
+
- spec/lib/amzn/instances_spec.rb
|
205
|
+
- spec/lib/cli_spec.rb
|
206
|
+
- spec/spec_helper.rb
|
207
|
+
homepage: https://github.com/AngryEgret/cloudspec
|
208
|
+
licenses:
|
209
|
+
- ''
|
210
|
+
metadata: {}
|
211
|
+
post_install_message:
|
212
|
+
rdoc_options: []
|
213
|
+
require_paths:
|
214
|
+
- lib
|
215
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
216
|
+
requirements:
|
217
|
+
- - ! '>='
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
version: '0'
|
220
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
221
|
+
requirements:
|
222
|
+
- - ! '>='
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '0'
|
225
|
+
requirements: []
|
226
|
+
rubyforge_project:
|
227
|
+
rubygems_version: 2.4.5
|
228
|
+
signing_key:
|
229
|
+
specification_version: 4
|
230
|
+
summary: Simple tool to harvest dead weight in AWS
|
231
|
+
test_files:
|
232
|
+
- features/instances.feature
|
233
|
+
- features/support/env.rb
|
234
|
+
- spec/cloudspec_spec.rb
|
235
|
+
- spec/lib/amzn/base_spec.rb
|
236
|
+
- spec/lib/amzn/instances_spec.rb
|
237
|
+
- spec/lib/cli_spec.rb
|
238
|
+
- spec/spec_helper.rb
|