bosh-verifyconnections 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/ChangeLog.md +5 -0
- data/Gemfile +6 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +69 -0
- data/Rakefile +2 -0
- data/bosh-verifyconnections.gemspec +27 -0
- data/lib/bosh/cli/commands/set_deployment_without_verification.rb +41 -0
- data/lib/bosh/cli/commands/verifyconnections.rb +66 -0
- data/lib/bosh/verifyconnections/models/deployment.rb +122 -0
- data/lib/bosh/verifyconnections/models/deployment_job.rb +64 -0
- data/lib/bosh/verifyconnections/models/director.rb +15 -0
- data/lib/bosh/verifyconnections/models.rb +3 -0
- data/lib/bosh/verifyconnections/version.rb +5 -0
- data/lib/bosh/verifyconnections.rb +7 -0
- data/lib/core-ext/hash_deep_merge.rb +34 -0
- data/lib/core-ext/hash_to_dotted_notation.rb +13 -0
- data/spec/fixtures/combination_of_correct_dns_and_static_ip_connections.yml +0 -0
- data/spec/fixtures/job_with_static_ips_but_not_referenced.yml +77 -0
- data/spec/fixtures/property_is_bosh_dns_but_not_for_bosh_job.yml +78 -0
- data/spec/fixtures/property_is_static_ip_but_not_assigned_to_job.yml +76 -0
- data/spec/models/deployment_job_spec.rb +37 -0
- data/spec/models/deployment_spec.rb +48 -0
- data/spec/spec_helper.rb +59 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 820df08ec6e4f8bb911adf3b77010afeaa429a7f
|
4
|
+
data.tar.gz: fa25cf1b90e232c949752801ff3c48f61763acc3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7a3bdfe381d651ef8df6a21234cde13ea895dcc7c1b425db01e82d6915d0a559a73dbd583d8106ecf45624ee427e348ec7fcfb2575f97105ce2195a62a5106f7
|
7
|
+
data.tar.gz: c7e8be56bdf4e734b52651d41a388953d692b0925d9f1b32a669db97e4e33a7fe031049df27b97e30387acdcc4eeb68812b8af56118736971ec26089ce42c120
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dr Nic Williams
|
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,69 @@
|
|
1
|
+
# bosh verify connections
|
2
|
+
|
3
|
+
BOSH CLI plugin that performs job interconnection verifications upon the target deployment
|
4
|
+
|
5
|
+
1. Show jobs with static IPs that aren't referenced elsewhere in the deployment properties
|
6
|
+
2. Show IPs in the deployment properties that aren't specified as static IPs by jobs
|
7
|
+
3. What internal DNS hostnames are specified but don't map to a job
|
8
|
+
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```
|
15
|
+
gem 'bosh-verifyconnections'
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
```
|
21
|
+
bosh verify connections
|
22
|
+
```
|
23
|
+
|
24
|
+
To see demonstration deployment manifests:
|
25
|
+
|
26
|
+
```
|
27
|
+
bosh deployment without verification spec/fixtures/job_with_static_ips_but_not_referenced.yml && bosh verify connections
|
28
|
+
```
|
29
|
+
|
30
|
+

|
31
|
+
|
32
|
+
From this example, perhaps `service.host` should be changed to `10.244.0.6`.
|
33
|
+
|
34
|
+
Others to try:
|
35
|
+
|
36
|
+
```
|
37
|
+
bosh deployment without verification spec/fixtures/property_is_bosh_dns_but_not_for_bosh_job.yml && bosh verify connections
|
38
|
+
bosh deployment without verification spec/fixtures/property_is_static_ip_but_not_assigned_to_job.yml && bosh verify connections
|
39
|
+
```
|
40
|
+
|
41
|
+
### Example
|
42
|
+
|
43
|
+
You can see that deploying Cloud Foundry on bosh-lite includes two static IPs that aren't referenced properties:
|
44
|
+
|
45
|
+
```
|
46
|
+
$ bosh verify connections
|
47
|
+
|
48
|
+
Current deployment is /Users/drnic/Projects/ruby/gems/cloudfoundry/bosh-lite/manifests/cf-manifest.yml
|
49
|
+
|
50
|
+
Job static IPs that are not being referenced by any properties:
|
51
|
+
+----------------------------------+--------------------------------+
|
52
|
+
| static ip | job/index |
|
53
|
+
+----------------------------------+--------------------------------+
|
54
|
+
| 10.244.0.34 | ha_proxy_z1/0 |
|
55
|
+
| 10.244.0.26 | runner_z1/0 |
|
56
|
+
+----------------------------------+--------------------------------+
|
57
|
+
```
|
58
|
+
|
59
|
+
The `ha_proxy_z1/0` static IP is used for the public access via `10.244.0.34.xip.io`, but theoretically the `runner_z1/0` job has no reason for a static IP.
|
60
|
+
|
61
|
+
From the [history of cf-release](https://github.com/cloudfoundry/cf-release/commit/858d1b7e1f0544fb9fd4d9a7a1608e542da6bcdd), the static IP was assigned to ease integration testing.
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
|
65
|
+
1. Fork it ( https://github.com/cloudfoundry-community/bosh-verifyconnections/fork )
|
66
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
67
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
68
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
69
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bosh/verifyconnections/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bosh-verifyconnections"
|
8
|
+
spec.version = Bosh::VerifyConnections::VERSION
|
9
|
+
spec.authors = ["Dr Nic Williams"]
|
10
|
+
spec.email = ["drnicwilliams@gmail.com"]
|
11
|
+
spec.summary = %q{Performs job interconnection verifications upon the target BOSH deployment manifest file.}
|
12
|
+
spec.description = %q{Performs job interconnection verifications upon the target BOSH deployment manifest file.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
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_runtime_dependency "bosh_cli", "~> 1.2200.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
26
|
+
spec.add_development_dependency "rspec-fire"
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Bosh::Cli::Command
|
2
|
+
class SetDeploymentWithoutVerification < Base
|
3
|
+
include Bosh::Cli::Validation
|
4
|
+
|
5
|
+
usage "deployment without verification"
|
6
|
+
desc "Ignore the director_uuid being incorrect and set the new deployment anyway"
|
7
|
+
def set_current_without_verification(filename = nil)
|
8
|
+
if filename.nil?
|
9
|
+
show_current
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
manifest_filename = find_deployment(filename)
|
14
|
+
|
15
|
+
unless File.exists?(manifest_filename)
|
16
|
+
err("Missing manifest for `#{filename}'")
|
17
|
+
end
|
18
|
+
|
19
|
+
manifest = load_yaml_file(manifest_filename)
|
20
|
+
|
21
|
+
unless manifest.is_a?(Hash)
|
22
|
+
err("Invalid manifest format")
|
23
|
+
end
|
24
|
+
|
25
|
+
manifest["director_uuid"] = director.uuid
|
26
|
+
|
27
|
+
tmp_manifest_filename = File.join("/tmp/", "#{manifest["name"]}.yml")
|
28
|
+
File.open(tmp_manifest_filename, "w") do |f|
|
29
|
+
f.puts(manifest.to_yaml)
|
30
|
+
end
|
31
|
+
|
32
|
+
# change director_uuid
|
33
|
+
# store manifest to tmp file
|
34
|
+
# set_deployment
|
35
|
+
|
36
|
+
say("Deployment set to `#{tmp_manifest_filename.make_yellow}'")
|
37
|
+
config.set_deployment(tmp_manifest_filename)
|
38
|
+
config.save
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "core-ext/hash_to_dotted_notation"
|
2
|
+
|
3
|
+
module Bosh::Cli::Command
|
4
|
+
# Performs job interconnection verifications upon the target deployment
|
5
|
+
# 1. Show jobs with static IPs that aren't referenced elsewhere in the deployment properties
|
6
|
+
# 2. Show IPs in the deployment properties that aren't specified as static IPs by jobs
|
7
|
+
# 3. What .*bosh hostnames are specified but don't map to a job
|
8
|
+
# 4. Hostnames that don't resolve to any IP
|
9
|
+
class VerifyConnections < Base
|
10
|
+
include Bosh::Cli::Validation
|
11
|
+
include BoshExtensions
|
12
|
+
|
13
|
+
usage "verify connections"
|
14
|
+
desc "Performs job interconnection verifications upon the target deployment"
|
15
|
+
def verify_connections
|
16
|
+
require "bosh/verifyconnections"
|
17
|
+
|
18
|
+
show_deployment
|
19
|
+
director_model = Bosh::VerifyConnections::Director.new(director.get_status)
|
20
|
+
deployment_model = Bosh::VerifyConnections::Deployment.new(deployment, director_model.domain_name)
|
21
|
+
|
22
|
+
errors = false
|
23
|
+
warnings = false
|
24
|
+
|
25
|
+
items = deployment_model.unreferenced_static_ips_with_job_index
|
26
|
+
if items.size > 0
|
27
|
+
warnings = true
|
28
|
+
nl
|
29
|
+
say "Job static IPs that are not being referenced by any properties:".make_yellow
|
30
|
+
view = table(items) do |t|
|
31
|
+
t.headings = ["static ip", "job/index"]
|
32
|
+
items.each { |item| t << item }
|
33
|
+
end
|
34
|
+
say(view)
|
35
|
+
end
|
36
|
+
|
37
|
+
items = deployment_model.property_static_ips_not_assigned_to_job
|
38
|
+
if items.size > 0
|
39
|
+
errors = true
|
40
|
+
nl
|
41
|
+
say "Properties referencing static IPs that do not belong to any job:".make_red
|
42
|
+
view = table(items) do |t|
|
43
|
+
t.headings = ["property", "static ip", "job name"]
|
44
|
+
items.each { |item| t << item }
|
45
|
+
end
|
46
|
+
say(view)
|
47
|
+
end
|
48
|
+
|
49
|
+
items = deployment_model.property_hostnames_not_mapping_to_job
|
50
|
+
if items.size > 0
|
51
|
+
errors = true
|
52
|
+
nl
|
53
|
+
say "Internal hostnames not mapping to any job:".make_red
|
54
|
+
view = table(items) do |t|
|
55
|
+
t.headings = ["property", "hostname", "job name"]
|
56
|
+
items.each { |item| t << item }
|
57
|
+
end
|
58
|
+
say(view)
|
59
|
+
end
|
60
|
+
|
61
|
+
nl
|
62
|
+
err("Please review errors above") if errors
|
63
|
+
warning("Please review warnings above") if warnings
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require "common/deep_copy"
|
2
|
+
require "core-ext/hash_deep_merge"
|
3
|
+
require "core-ext/hash_to_dotted_notation"
|
4
|
+
|
5
|
+
module Bosh::VerifyConnections
|
6
|
+
class Deployment
|
7
|
+
include BoshExtensions
|
8
|
+
|
9
|
+
attr_reader :deployment, :jobs, :domain_name
|
10
|
+
|
11
|
+
def initialize(deployment_file, domain_name)
|
12
|
+
parse_deployment_file(deployment_file)
|
13
|
+
@domain_name = domain_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def deployment_name
|
17
|
+
@deployment["name"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def hostnames_offered
|
21
|
+
jobs.inject([]) do |dns, job|
|
22
|
+
dns.push(*job.hostnames_offered(deployment_name, domain_name))
|
23
|
+
dns
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def static_ips_assigned
|
28
|
+
jobs.inject([]) do |ips, job|
|
29
|
+
ips.push(*job.static_ips_assigned)
|
30
|
+
ips
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def static_ips_with_job_index
|
35
|
+
jobs.inject([]) do |ips, job|
|
36
|
+
job.static_ips_assigned.each_with_index do |ip, index|
|
37
|
+
job_index = "#{job.job_name}/#{index}"
|
38
|
+
ips << [ip, job_index]
|
39
|
+
end
|
40
|
+
ips
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return Array [ip, job_index]
|
45
|
+
def unreferenced_static_ips_with_job_index
|
46
|
+
static_ips_with_job_index.reject do |ip, _|
|
47
|
+
all_property_values.include?(ip)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_properties_by_job
|
52
|
+
@all_properties_by_job ||= begin
|
53
|
+
all = { "global" => global_properties }
|
54
|
+
jobs.each do |job|
|
55
|
+
all[job.job_name] = job.job_properties
|
56
|
+
end
|
57
|
+
all
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def all_property_values
|
62
|
+
@all_property_values ||=
|
63
|
+
all_properties_by_job.values.map(&:to_dotted_hash).map(&:values).flatten
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return Array [ip, job_name, property_name]
|
67
|
+
def property_static_ips_not_assigned_to_job
|
68
|
+
result = []
|
69
|
+
all_properties_by_job.each do |job_name, properties|
|
70
|
+
properties.to_dotted_hash.each do |key, value|
|
71
|
+
result << [key, value, job_name] if invalid_static_ip?(value)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return Array [hostname, job_name, property_name]
|
78
|
+
def property_hostnames_not_mapping_to_job
|
79
|
+
result = []
|
80
|
+
all_properties_by_job.each do |job_name, properties|
|
81
|
+
properties.to_dotted_hash.each do |key, value|
|
82
|
+
result << [key, value, job_name] if invalid_internal_hostname?(value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
def job(job_name)
|
89
|
+
jobs.find { |job| job.job_name == job_name }
|
90
|
+
end
|
91
|
+
|
92
|
+
def global_properties
|
93
|
+
@deployment["properties"]
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def parse_deployment_file(deployment_file)
|
98
|
+
@deployment_file = deployment_file
|
99
|
+
@deployment = load_yaml_file(@deployment_file)
|
100
|
+
@jobs = @deployment["jobs"].map do |manifest_job|
|
101
|
+
DeploymentJob.new(manifest_job)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def invalid_static_ip?(value)
|
106
|
+
return false unless value.is_a? String
|
107
|
+
parts = value.split(".")
|
108
|
+
return false if parts.size != 4
|
109
|
+
parts.each do |part|
|
110
|
+
return false if part.to_i.to_s != part
|
111
|
+
end
|
112
|
+
return !static_ips_assigned.include?(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
def invalid_internal_hostname?(value)
|
116
|
+
return false unless value.is_a? String
|
117
|
+
return false unless (value =~ /\.#{domain_name}$/)
|
118
|
+
return !hostnames_offered.include?(value)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class Bosh::VerifyConnections::DeploymentJob
|
2
|
+
class DnsInvalidCanonicalName < StandardError; end
|
3
|
+
|
4
|
+
def initialize(job_from_manifest)
|
5
|
+
@from_manifest = job_from_manifest
|
6
|
+
end
|
7
|
+
|
8
|
+
def job_name
|
9
|
+
@from_manifest["name"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def instances
|
13
|
+
@from_manifest["instances"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def networks
|
17
|
+
@from_manifest["networks"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def static_ips_assigned
|
21
|
+
networks.inject([]) do |ips, network|
|
22
|
+
if network["static_ips"]
|
23
|
+
ips.push(*network["static_ips"])
|
24
|
+
end
|
25
|
+
ips
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def hostnames_offered(deployment_name, dns_suffix)
|
30
|
+
deployment_name = dns_canonical(deployment_name)
|
31
|
+
dns_suffix = dns_canonical(dns_suffix)
|
32
|
+
job_name = dns_canonical(self.job_name)
|
33
|
+
networks.inject([]) do |hostnames, network|
|
34
|
+
network_name = dns_canonical(network["name"])
|
35
|
+
0.upto(instances-1) do |n|
|
36
|
+
hostnames << [n, job_name, network_name, deployment_name, dns_suffix].join(".")
|
37
|
+
end
|
38
|
+
hostnames
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def job_properties
|
43
|
+
@from_manifest["properties"] || {}
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def dns_canonical(string)
|
48
|
+
unless string
|
49
|
+
raise DnsInvalidCanonicalName, "Must provide a string"
|
50
|
+
end
|
51
|
+
# a-z, 0-9, -, case insensitive, and must start with a letter
|
52
|
+
string = string.downcase.gsub(/_/, "-").gsub(/[^a-z0-9-]/, "")
|
53
|
+
if string =~ /^(\d|-)/
|
54
|
+
raise DnsInvalidCanonicalName,
|
55
|
+
"Invalid DNS canonical name `#{string}', must begin with a letter"
|
56
|
+
end
|
57
|
+
if string =~ /-$/
|
58
|
+
raise DnsInvalidCanonicalName,
|
59
|
+
"Invalid DNS canonical name `#{string}', can't end with a hyphen"
|
60
|
+
end
|
61
|
+
string
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bosh::VerifyConnections
|
2
|
+
class Director
|
3
|
+
def initialize(status)
|
4
|
+
@status = status
|
5
|
+
end
|
6
|
+
|
7
|
+
def dns?
|
8
|
+
@status["features"]["dns"] && @status["features"]["dns"]["status"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def domain_name
|
12
|
+
@status["features"]["dns"]["extras"]["domain_name"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# https://github.com/Offirmo/hash-deep-merge/blob/master/lib/hash_deep_merge.rb
|
2
|
+
class Hash
|
3
|
+
|
4
|
+
def deep_merge!(specialized_hash)
|
5
|
+
return internal_deep_merge!(self, specialized_hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
def deep_merge(specialized_hash)
|
10
|
+
return internal_deep_merge!(Hash.new.replace(self), specialized_hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# better, recursive, preserving method
|
16
|
+
# OK OK this is not the most efficient algorithm,
|
17
|
+
# but at last it's *perfectly clear and understandable*
|
18
|
+
# so fork and improve if you need 5% more speed, ok ?
|
19
|
+
def internal_deep_merge!(source_hash, specialized_hash)
|
20
|
+
specialized_hash.each_pair do |rkey, rval|
|
21
|
+
if source_hash.has_key?(rkey) then
|
22
|
+
if rval.is_a?(Hash) and source_hash[rkey].is_a?(Hash) then
|
23
|
+
internal_deep_merge!(source_hash[rkey], rval)
|
24
|
+
elsif rval == source_hash[rkey] then
|
25
|
+
else
|
26
|
+
source_hash[rkey] = rval
|
27
|
+
end
|
28
|
+
else
|
29
|
+
source_hash[rkey] = rval
|
30
|
+
end
|
31
|
+
end
|
32
|
+
source_hash
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# From http://raycoding.net/2013/08/26/nested-hash-to-dotted-notation-hash-in-ruby/
|
2
|
+
class Hash
|
3
|
+
def to_dotted_hash(recursive_key = "")
|
4
|
+
self.each_with_object({}) do |(k, v), ret|
|
5
|
+
key = recursive_key + k.to_s
|
6
|
+
if v.is_a? Hash
|
7
|
+
ret.merge!(v.to_dotted_hash(key + "."))
|
8
|
+
else
|
9
|
+
ret[key] = v
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
File without changes
|
@@ -0,0 +1,77 @@
|
|
1
|
+
---
|
2
|
+
name: job_with_static_ips_but_not_referenced
|
3
|
+
releases:
|
4
|
+
- name: cf
|
5
|
+
version: latest
|
6
|
+
|
7
|
+
networks:
|
8
|
+
- name: cf1
|
9
|
+
subnets:
|
10
|
+
# static
|
11
|
+
- cloud_properties:
|
12
|
+
name: random
|
13
|
+
range: 10.244.0.0/30
|
14
|
+
reserved:
|
15
|
+
- 10.244.0.1
|
16
|
+
static:
|
17
|
+
- 10.244.0.2
|
18
|
+
- cloud_properties:
|
19
|
+
name: random
|
20
|
+
range: 10.244.0.4/30
|
21
|
+
reserved:
|
22
|
+
- 10.244.0.5
|
23
|
+
static:
|
24
|
+
- 10.244.0.6
|
25
|
+
- cloud_properties:
|
26
|
+
name: random
|
27
|
+
range: 10.244.0.8/30
|
28
|
+
reserved:
|
29
|
+
- 10.244.0.9
|
30
|
+
static:
|
31
|
+
- 10.244.0.10
|
32
|
+
|
33
|
+
# dynamic
|
34
|
+
- cloud_properties:
|
35
|
+
name: random
|
36
|
+
range: 10.244.0.128/30
|
37
|
+
reserved:
|
38
|
+
- 10.244.0.129
|
39
|
+
static: []
|
40
|
+
- cloud_properties:
|
41
|
+
name: random
|
42
|
+
range: 10.244.0.132/30
|
43
|
+
reserved:
|
44
|
+
- 10.244.0.133
|
45
|
+
static: []
|
46
|
+
- cloud_properties:
|
47
|
+
name: random
|
48
|
+
range: 10.244.0.136/30
|
49
|
+
reserved:
|
50
|
+
- 10.244.0.137
|
51
|
+
|
52
|
+
jobs:
|
53
|
+
- name: ephemeral
|
54
|
+
instances: 2
|
55
|
+
templates:
|
56
|
+
- name: cloud_controller_ng
|
57
|
+
networks:
|
58
|
+
- name: cf1
|
59
|
+
- name: service
|
60
|
+
instances: 1
|
61
|
+
templates:
|
62
|
+
- name: postgres
|
63
|
+
networks:
|
64
|
+
- name: cf1
|
65
|
+
default: [dns, gateway]
|
66
|
+
- name: floating
|
67
|
+
static_ips:
|
68
|
+
- 10.244.0.6
|
69
|
+
properties:
|
70
|
+
service:
|
71
|
+
port: 4444
|
72
|
+
extra: property
|
73
|
+
|
74
|
+
properties:
|
75
|
+
service:
|
76
|
+
host: 10.244.0.10
|
77
|
+
port: 3333
|
@@ -0,0 +1,78 @@
|
|
1
|
+
---
|
2
|
+
name: job_with_static_ips_but_not_referenced
|
3
|
+
releases:
|
4
|
+
- name: cf
|
5
|
+
version: latest
|
6
|
+
|
7
|
+
networks:
|
8
|
+
- name: cf1
|
9
|
+
subnets:
|
10
|
+
# static
|
11
|
+
- cloud_properties:
|
12
|
+
name: random
|
13
|
+
range: 10.244.0.0/30
|
14
|
+
reserved:
|
15
|
+
- 10.244.0.1
|
16
|
+
static:
|
17
|
+
- 10.244.0.2
|
18
|
+
- cloud_properties:
|
19
|
+
name: random
|
20
|
+
range: 10.244.0.4/30
|
21
|
+
reserved:
|
22
|
+
- 10.244.0.5
|
23
|
+
static:
|
24
|
+
- 10.244.0.6
|
25
|
+
- cloud_properties:
|
26
|
+
name: random
|
27
|
+
range: 10.244.0.8/30
|
28
|
+
reserved:
|
29
|
+
- 10.244.0.9
|
30
|
+
static:
|
31
|
+
- 10.244.0.10
|
32
|
+
|
33
|
+
# dynamic
|
34
|
+
- cloud_properties:
|
35
|
+
name: random
|
36
|
+
range: 10.244.0.128/30
|
37
|
+
reserved:
|
38
|
+
- 10.244.0.129
|
39
|
+
static: []
|
40
|
+
- cloud_properties:
|
41
|
+
name: random
|
42
|
+
range: 10.244.0.132/30
|
43
|
+
reserved:
|
44
|
+
- 10.244.0.133
|
45
|
+
static: []
|
46
|
+
- cloud_properties:
|
47
|
+
name: random
|
48
|
+
range: 10.244.0.136/30
|
49
|
+
reserved:
|
50
|
+
- 10.244.0.137
|
51
|
+
|
52
|
+
jobs:
|
53
|
+
- name: ephemeral
|
54
|
+
instances: 2
|
55
|
+
templates:
|
56
|
+
- name: cloud_controller_ng
|
57
|
+
networks:
|
58
|
+
- name: cf1
|
59
|
+
- name: service
|
60
|
+
instances: 1
|
61
|
+
templates:
|
62
|
+
- name: postgres
|
63
|
+
networks:
|
64
|
+
- name: cf1
|
65
|
+
default: [dns, gateway]
|
66
|
+
- name: floating
|
67
|
+
static_ips:
|
68
|
+
- 10.244.0.6
|
69
|
+
properties:
|
70
|
+
service:
|
71
|
+
port: 4444
|
72
|
+
host: 0.service.floating.job-with-static-ips-but-not-referenced.microbosh
|
73
|
+
extra: property
|
74
|
+
|
75
|
+
properties:
|
76
|
+
service:
|
77
|
+
host: 0.some-service.cf1.job-with-static-ips-but-not-referenced.microbosh
|
78
|
+
port: 3333
|
@@ -0,0 +1,76 @@
|
|
1
|
+
---
|
2
|
+
name: job_with_static_ips_but_not_referenced
|
3
|
+
releases:
|
4
|
+
- name: cf
|
5
|
+
version: latest
|
6
|
+
|
7
|
+
networks:
|
8
|
+
- name: cf1
|
9
|
+
subnets:
|
10
|
+
# static
|
11
|
+
- cloud_properties:
|
12
|
+
name: random
|
13
|
+
range: 10.244.0.0/30
|
14
|
+
reserved:
|
15
|
+
- 10.244.0.1
|
16
|
+
static:
|
17
|
+
- 10.244.0.2
|
18
|
+
- cloud_properties:
|
19
|
+
name: random
|
20
|
+
range: 10.244.0.4/30
|
21
|
+
reserved:
|
22
|
+
- 10.244.0.5
|
23
|
+
static:
|
24
|
+
- 10.244.0.6
|
25
|
+
- cloud_properties:
|
26
|
+
name: random
|
27
|
+
range: 10.244.0.8/30
|
28
|
+
reserved:
|
29
|
+
- 10.244.0.9
|
30
|
+
static:
|
31
|
+
- 10.244.0.10
|
32
|
+
|
33
|
+
# dynamic
|
34
|
+
- cloud_properties:
|
35
|
+
name: random
|
36
|
+
range: 10.244.0.128/30
|
37
|
+
reserved:
|
38
|
+
- 10.244.0.129
|
39
|
+
static: []
|
40
|
+
- cloud_properties:
|
41
|
+
name: random
|
42
|
+
range: 10.244.0.132/30
|
43
|
+
reserved:
|
44
|
+
- 10.244.0.133
|
45
|
+
static: []
|
46
|
+
- cloud_properties:
|
47
|
+
name: random
|
48
|
+
range: 10.244.0.136/30
|
49
|
+
reserved:
|
50
|
+
- 10.244.0.137
|
51
|
+
|
52
|
+
jobs:
|
53
|
+
- name: ephemeral
|
54
|
+
instances: 2
|
55
|
+
templates:
|
56
|
+
- name: cloud_controller_ng
|
57
|
+
networks:
|
58
|
+
- name: cf1
|
59
|
+
properties:
|
60
|
+
service:
|
61
|
+
host: 10.244.0.6
|
62
|
+
- name: service
|
63
|
+
instances: 1
|
64
|
+
templates:
|
65
|
+
- name: postgres
|
66
|
+
networks:
|
67
|
+
- name: cf1
|
68
|
+
default: [dns, gateway]
|
69
|
+
- name: floating
|
70
|
+
static_ips:
|
71
|
+
- 10.244.0.6
|
72
|
+
|
73
|
+
properties:
|
74
|
+
service:
|
75
|
+
host: 10.244.0.10
|
76
|
+
port: 3333
|
@@ -0,0 +1,37 @@
|
|
1
|
+
describe Bosh::VerifyConnections::DeploymentJob do
|
2
|
+
context "static_ips" do
|
3
|
+
subject do
|
4
|
+
Bosh::VerifyConnections::DeploymentJob.new({
|
5
|
+
"name" => "service_name",
|
6
|
+
"instances" => 1,
|
7
|
+
"templates" => [{
|
8
|
+
"name" => "postgres"
|
9
|
+
}],
|
10
|
+
"networks" => [
|
11
|
+
{
|
12
|
+
"name" => "internal_network",
|
13
|
+
"default" => ["dns", "gateway"]
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"name" => "floating",
|
17
|
+
"static_ips" => ["10.244.0.6"]
|
18
|
+
}
|
19
|
+
],
|
20
|
+
"properties" => {
|
21
|
+
"port" => 3333,
|
22
|
+
"nested" => {
|
23
|
+
"key" => "value"
|
24
|
+
}
|
25
|
+
}
|
26
|
+
})
|
27
|
+
end
|
28
|
+
it { expect(subject.instances).to eq(1) }
|
29
|
+
it { expect(subject.static_ips_assigned).to eq(["10.244.0.6"])}
|
30
|
+
it { expect(subject.hostnames_offered("my_deployment", "suffix")).to eq([
|
31
|
+
"0.service-name.internal-network.my-deployment.suffix",
|
32
|
+
"0.service-name.floating.my-deployment.suffix"
|
33
|
+
])}
|
34
|
+
it { expect(subject.job_properties).to eq({"port" => 3333, "nested" => {"key" => "value"}}) }
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
describe Bosh::VerifyConnections::Deployment do
|
2
|
+
context "job_with_static_ips_but_not_referenced" do
|
3
|
+
let(:manifest) { spec_fixture("job_with_static_ips_but_not_referenced.yml") }
|
4
|
+
subject { Bosh::VerifyConnections::Deployment.new(manifest, "microbosh") }
|
5
|
+
|
6
|
+
it { expect(subject.jobs.size).to eq(2) }
|
7
|
+
it { expect(subject.hostnames_offered).to eq([
|
8
|
+
"0.ephemeral.cf1.job-with-static-ips-but-not-referenced.microbosh",
|
9
|
+
"1.ephemeral.cf1.job-with-static-ips-but-not-referenced.microbosh",
|
10
|
+
"0.service.cf1.job-with-static-ips-but-not-referenced.microbosh",
|
11
|
+
"0.service.floating.job-with-static-ips-but-not-referenced.microbosh"
|
12
|
+
])}
|
13
|
+
|
14
|
+
it { expect(subject.static_ips_assigned).to eq(["10.244.0.6"])}
|
15
|
+
it { expect(subject.global_properties).to eq({
|
16
|
+
"service" => {
|
17
|
+
"host" => "10.244.0.10",
|
18
|
+
"port" => 3333
|
19
|
+
}
|
20
|
+
})}
|
21
|
+
|
22
|
+
it { expect(subject.unreferenced_static_ips_with_job_index).to eq([["10.244.0.6", "service/0"]]) }
|
23
|
+
it { expect(subject.property_static_ips_not_assigned_to_job).to eq([["service.host", "10.244.0.10", "global"]]) }
|
24
|
+
it { expect(subject.property_hostnames_not_mapping_to_job).to eq([]) }
|
25
|
+
end
|
26
|
+
|
27
|
+
context "property_is_bosh_dns_but_not_for_bosh_job" do
|
28
|
+
let(:manifest) { spec_fixture("property_is_bosh_dns_but_not_for_bosh_job.yml") }
|
29
|
+
subject { Bosh::VerifyConnections::Deployment.new(manifest, "microbosh") }
|
30
|
+
|
31
|
+
it { expect(subject.unreferenced_static_ips_with_job_index).to eq([["10.244.0.6", "service/0"]]) }
|
32
|
+
it { expect(subject.property_static_ips_not_assigned_to_job).to eq([]) }
|
33
|
+
it { expect(subject.property_hostnames_not_mapping_to_job).to eq([
|
34
|
+
["service.host", "0.some-service.cf1.job-with-static-ips-but-not-referenced.microbosh", "global"]
|
35
|
+
]) }
|
36
|
+
end
|
37
|
+
|
38
|
+
context "property_is_static_ip_but_not_assigned_to_job" do
|
39
|
+
let(:manifest) { spec_fixture("property_is_static_ip_but_not_assigned_to_job.yml") }
|
40
|
+
subject { Bosh::VerifyConnections::Deployment.new(manifest, "microbosh") }
|
41
|
+
|
42
|
+
it { expect(subject.unreferenced_static_ips_with_job_index).to eq([]) }
|
43
|
+
it { expect(subject.property_static_ips_not_assigned_to_job).to eq([["service.host", "10.244.0.10", "global"]]) }
|
44
|
+
it { expect(subject.property_hostnames_not_mapping_to_job).to eq([]) }
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright (c) 2012-2013 Stark & Wayne, LLC
|
2
|
+
|
3
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
require "bundler"
|
7
|
+
Bundler.setup(:default, :test)
|
8
|
+
|
9
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
10
|
+
|
11
|
+
require "rspec/core"
|
12
|
+
require 'rspec/fire'
|
13
|
+
|
14
|
+
# for the #sh helper
|
15
|
+
require "rake"
|
16
|
+
require "rake/file_utils"
|
17
|
+
|
18
|
+
# bosh_cli
|
19
|
+
require "cli"
|
20
|
+
|
21
|
+
require "bosh/verifyconnections"
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
config.include(RSpec::Fire)
|
25
|
+
end
|
26
|
+
|
27
|
+
# load all files in spec/support/* (but not lower down)
|
28
|
+
Dir[File.dirname(__FILE__) + '/support/*'].each do |path|
|
29
|
+
require path unless File.directory?(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def spec_fixture(filename)
|
33
|
+
File.expand_path("../fixtures/#{filename}", __FILE__)
|
34
|
+
end
|
35
|
+
|
36
|
+
def setup_home_dir
|
37
|
+
home_dir = File.expand_path("../../tmp/home", __FILE__)
|
38
|
+
FileUtils.rm_rf(home_dir)
|
39
|
+
FileUtils.mkdir_p(home_dir)
|
40
|
+
ENV['HOME'] = home_dir
|
41
|
+
end
|
42
|
+
|
43
|
+
# returns the file path to a file
|
44
|
+
# in the fake $HOME folder
|
45
|
+
def home_file(*path)
|
46
|
+
File.join(ENV['HOME'], *path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def in_home_dir(&block)
|
50
|
+
FileUtils.chdir(home_file, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_tmp_file_path(content)
|
54
|
+
tmp_file = File.open(File.join(Dir.mktmpdir, "tmp"), "w")
|
55
|
+
tmp_file.write(content)
|
56
|
+
tmp_file.close
|
57
|
+
|
58
|
+
tmp_file.path
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bosh-verifyconnections
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dr Nic Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bosh_cli
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2200.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2200.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.14'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.14'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-fire
|
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
|
+
description: Performs job interconnection verifications upon the target BOSH deployment
|
84
|
+
manifest file.
|
85
|
+
email:
|
86
|
+
- drnicwilliams@gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- .rspec
|
93
|
+
- ChangeLog.md
|
94
|
+
- Gemfile
|
95
|
+
- Guardfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bosh-verifyconnections.gemspec
|
100
|
+
- lib/bosh/cli/commands/set_deployment_without_verification.rb
|
101
|
+
- lib/bosh/cli/commands/verifyconnections.rb
|
102
|
+
- lib/bosh/verifyconnections.rb
|
103
|
+
- lib/bosh/verifyconnections/models.rb
|
104
|
+
- lib/bosh/verifyconnections/models/deployment.rb
|
105
|
+
- lib/bosh/verifyconnections/models/deployment_job.rb
|
106
|
+
- lib/bosh/verifyconnections/models/director.rb
|
107
|
+
- lib/bosh/verifyconnections/version.rb
|
108
|
+
- lib/core-ext/hash_deep_merge.rb
|
109
|
+
- lib/core-ext/hash_to_dotted_notation.rb
|
110
|
+
- spec/fixtures/combination_of_correct_dns_and_static_ip_connections.yml
|
111
|
+
- spec/fixtures/job_with_static_ips_but_not_referenced.yml
|
112
|
+
- spec/fixtures/property_is_bosh_dns_but_not_for_bosh_job.yml
|
113
|
+
- spec/fixtures/property_is_static_ip_but_not_assigned_to_job.yml
|
114
|
+
- spec/models/deployment_job_spec.rb
|
115
|
+
- spec/models/deployment_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
homepage: ''
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.2.2
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Performs job interconnection verifications upon the target BOSH deployment
|
141
|
+
manifest file.
|
142
|
+
test_files:
|
143
|
+
- spec/fixtures/combination_of_correct_dns_and_static_ip_connections.yml
|
144
|
+
- spec/fixtures/job_with_static_ips_but_not_referenced.yml
|
145
|
+
- spec/fixtures/property_is_bosh_dns_but_not_for_bosh_job.yml
|
146
|
+
- spec/fixtures/property_is_static_ip_but_not_assigned_to_job.yml
|
147
|
+
- spec/models/deployment_job_spec.rb
|
148
|
+
- spec/models/deployment_spec.rb
|
149
|
+
- spec/spec_helper.rb
|