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,4 @@
1
+ module BoshVerifyManifest
2
+ # The current version of bosh-verify-manifest
3
+ VERSION = '0.1.0'
4
+ 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: []