bosh-verify-manifest 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.
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'bosh-verify-manifest/helpers'
|
2
|
+
require_relative 'bosh-verify-manifest/assertions'
|
3
|
+
require_relative 'bosh-verify-manifest/infections'
|
4
|
+
require_relative 'bosh-verify-manifest/version'
|
5
|
+
|
6
|
+
module BoshVerifyManifest
|
7
|
+
require 'minitest/spec'
|
8
|
+
|
9
|
+
class Spec < MiniTest::Spec
|
10
|
+
include Assertions
|
11
|
+
include Infections
|
12
|
+
|
13
|
+
def manifest_path
|
14
|
+
"#{self.class.name.split('::').last}.yml"
|
15
|
+
end
|
16
|
+
|
17
|
+
def manifest
|
18
|
+
YAML::load(File.read(manifest_path))
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
MiniTest::Spec.register_spec_type(/^bosh_manifest::/, BoshVerifyManifest::Spec)
|
24
|
+
end
|
25
|
+
|
26
|
+
module Kernel
|
27
|
+
def describe_bosh_manifest(desc, additional_desc = nil, &block)
|
28
|
+
describe("bosh_manifest::#{desc}", additional_desc, &block)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'uuidtools'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module BoshVerifyManifest
|
6
|
+
module Assertions
|
7
|
+
|
8
|
+
include Helpers
|
9
|
+
|
10
|
+
def assert_declares_all_resource_pools(manifest)
|
11
|
+
undeclared_pools(manifest).each do |pool|
|
12
|
+
flunk "The '#{pool}' pool referred to does not exist"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def assert_fills_resource_pools(manifest)
|
17
|
+
usage = pool_usage(manifest)
|
18
|
+
pool_sizes(manifest).each_pair do |pool, instances|
|
19
|
+
assert instances == usage[pool],
|
20
|
+
"The '#{pool}' pool is not full (size: #{instances}, wanted: #{usage[pool]})"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_has_name(manifest)
|
25
|
+
assert manifest.key?('name'), 'The manifest does not have a name'
|
26
|
+
end
|
27
|
+
|
28
|
+
def assert_job_addresses_are_appropriate(manifest)
|
29
|
+
manifest['jobs'].each do |job|
|
30
|
+
job['networks'].select{|n| n['static_ips']}.each do |network|
|
31
|
+
ranges = network(manifest, network['name'])['subnets'].map{|s| s['range']}
|
32
|
+
assert ranges.any?{|r| addresses_in_range?(network['static_ips'], r)},
|
33
|
+
"The job '#{job['name']}' has static_ips that are not valid for the network '#{network['name']}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def assert_range_includes_gateway(subnet)
|
39
|
+
if subnet['gateway']
|
40
|
+
assert IPAddr.new(subnet['range']).include?(IPAddr.new(subnet['gateway']))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def assert_specifies_director_uuid(manifest)
|
45
|
+
assert manifest.key?('director_uuid'), 'The manifest does not specify the UUID of the bosh director'
|
46
|
+
begin
|
47
|
+
UUIDTools::UUID.parse(manifest['director_uuid'])
|
48
|
+
rescue ArgumentError
|
49
|
+
flunk 'The bosh director UUID is not a well-formed UUID'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def assert_subnet_addresses_in_range(subnet)
|
54
|
+
%w{reserved static}.each do |address_type|
|
55
|
+
Array(subnet[address_type]).each do |addresses|
|
56
|
+
assert addresses_in_range?(addresses, subnet['range']),
|
57
|
+
"The subnet #{address_type} addresses are not within the range #{subnet['range']}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def assert_subnet_ranges_do_not_overlap(subnets)
|
63
|
+
ranges = subnets.map{|s| s['range']}
|
64
|
+
ranges.permutation(2).each do |range_a, range_b|
|
65
|
+
refute IPAddr.new(range_a).include?(IPAddr.new(range_b)),
|
66
|
+
"The subnet ranges #{[range_a, range_b].sort.join(' and ')} overlap"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def assert_subnets_are_consistent(networks)
|
71
|
+
networks.each do |network|
|
72
|
+
if network['subnets']
|
73
|
+
network['subnets'].each do |subnet|
|
74
|
+
assert_range_includes_gateway(subnet)
|
75
|
+
assert_subnet_addresses_in_range(subnet)
|
76
|
+
end
|
77
|
+
assert_subnet_ranges_do_not_overlap(network['subnets'])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def refute_exceeds_resource_pools(manifest)
|
83
|
+
usage = pool_usage(manifest)
|
84
|
+
pool_sizes(manifest).each_pair do |pool, instances|
|
85
|
+
assert instances >= usage[pool],
|
86
|
+
"The '#{pool}' pool is not large enough (size: #{instances}, wanted: #{usage[pool]})"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module BoshVerifyManifest
|
4
|
+
module Assertions
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
def addresses_in_range?(addresses, range)
|
8
|
+
unless range.include?('/') and range =~ %r{/[0-9]+$}
|
9
|
+
raise ArgumentError, 'Range must be specified in CIDR format.'
|
10
|
+
end
|
11
|
+
# Can't get the netmask from an IPAddr instance without monkey patching
|
12
|
+
# Consider using an alternate library for this functionality
|
13
|
+
mask = range.split('/').last.to_i
|
14
|
+
if addresses.include?('-')
|
15
|
+
first_and_last_addresses = addresses.split('-').map do |address|
|
16
|
+
IPAddr.new(address.strip).to_s
|
17
|
+
end.uniq
|
18
|
+
expanded_range = IPAddr.new(range).to_range.to_a.map{|i| i.to_s}
|
19
|
+
(first_and_last_addresses & expanded_range) == first_and_last_addresses
|
20
|
+
else
|
21
|
+
Array(addresses).all? do |address|
|
22
|
+
if address.include?('-')
|
23
|
+
addresses_in_range?(address, range)
|
24
|
+
else
|
25
|
+
IPAddr.new(range).include?(address)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def network(manifest, name)
|
32
|
+
manifest['networks'].find{|n| n['name'] == name} || raise(
|
33
|
+
"Network '#{name}' not found in the manifest")
|
34
|
+
end
|
35
|
+
|
36
|
+
def pool_names(manifest)
|
37
|
+
manifest['resource_pools'].map{|pool| pool['name']}
|
38
|
+
end
|
39
|
+
|
40
|
+
def pool_sizes(manifest)
|
41
|
+
Hash[manifest['resource_pools'].map{|pool| [pool['name'], pool['size']]}]
|
42
|
+
end
|
43
|
+
|
44
|
+
def pool_usage(manifest)
|
45
|
+
Hash[manifest['jobs'].group_by do |job|
|
46
|
+
job['resource_pool']
|
47
|
+
end.map do |pool, jobs|
|
48
|
+
[pool, jobs.inject(0){|count, job| count += job['instances']}]
|
49
|
+
end]
|
50
|
+
end
|
51
|
+
|
52
|
+
def undeclared_pools(manifest)
|
53
|
+
pool_usage(manifest).keys.sort - pool_names(manifest).sort
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module BoshVerifyManifest
|
2
|
+
module Infections
|
3
|
+
|
4
|
+
Hash.infect_an_assertion :assert_fills_resource_pools, 'must_fill_resource_pools', :only_one_argument
|
5
|
+
Hash.infect_an_assertion :assert_has_name, 'must_be_named', :only_one_argument
|
6
|
+
Hash.infect_an_assertion :assert_job_addresses_are_appropriate, 'must_have_appropriate_job_addresses', :only_one_argument
|
7
|
+
Hash.infect_an_assertion :assert_specifies_director_uuid, 'must_specify_director_uuid', :only_one_argument
|
8
|
+
Array.infect_an_assertion :assert_subnets_are_consistent, 'must_have_consistent_subnets', :only_one_argument
|
9
|
+
Hash.infect_an_assertion :refute_exceeds_resource_pools, 'wont_exceed_resource_pools', :only_one_argument
|
10
|
+
Hash.infect_an_assertion :assert_declares_all_resource_pools, 'must_declare_all_resource_pools', :only_one_argument
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'bosh-verify-manifest/helpers'
|
3
|
+
require 'bosh-verify-manifest/assertions'
|
4
|
+
|
5
|
+
module Bosh::Cli::Command
|
6
|
+
class VerifyManifest < Base
|
7
|
+
|
8
|
+
include MiniTest::Assertions
|
9
|
+
include BoshVerifyManifest::Assertions
|
10
|
+
|
11
|
+
def manifest
|
12
|
+
@manifest ||= YAML::load(File.read(deployment))
|
13
|
+
end
|
14
|
+
|
15
|
+
usage "verify manifest"
|
16
|
+
desc "Check the BOSH manifest for common errors"
|
17
|
+
def verify_manifest
|
18
|
+
deployment_required
|
19
|
+
begin
|
20
|
+
assert_has_name(manifest)
|
21
|
+
assert_specifies_director_uuid(manifest)
|
22
|
+
assert_subnets_are_consistent(manifest['networks'])
|
23
|
+
assert_job_addresses_are_appropriate(manifest)
|
24
|
+
assert_declares_all_resource_pools(manifest)
|
25
|
+
refute_exceeds_resource_pools(manifest)
|
26
|
+
assert_fills_resource_pools(manifest)
|
27
|
+
rescue MiniTest::Assertion => a
|
28
|
+
err a.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bosh-verify-manifest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Crump
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bosh_cli
|
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: minitest
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 4.7.4
|
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: 4.7.4
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: uuidtools
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.1.3
|
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: 2.1.3
|
62
|
+
description: Validate BOSH deployment manifests.
|
63
|
+
email:
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- lib/bosh-verify-manifest.rb
|
69
|
+
- lib/bosh/cli/commands/verify.rb
|
70
|
+
- lib/bosh-verify-manifest/infections.rb
|
71
|
+
- lib/bosh-verify-manifest/version.rb
|
72
|
+
- lib/bosh-verify-manifest/assertions.rb
|
73
|
+
- lib/bosh-verify-manifest/helpers.rb
|
74
|
+
homepage: http://github.com/cloudcredo/bosh-verify-manifest
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
hash: -1852599430210049349
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
hash: -1852599430210049349
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.25
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: bosh-verify-manifest-0.1.0
|
105
|
+
test_files: []
|