ridley 0.10.2 → 0.11.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/README.md +147 -216
  2. data/lib/ridley.rb +2 -0
  3. data/lib/ridley/bootstrap_bindings/unix_template_binding.rb +21 -25
  4. data/lib/ridley/bootstrap_bindings/windows_template_binding.rb +29 -34
  5. data/lib/ridley/bootstrapper.rb +2 -2
  6. data/lib/ridley/bootstrapper/context.rb +5 -5
  7. data/lib/ridley/chef.rb +0 -1
  8. data/lib/ridley/chef/cookbook.rb +0 -9
  9. data/lib/ridley/chef_object.rb +128 -0
  10. data/lib/ridley/chef_objects.rb +3 -0
  11. data/lib/ridley/chef_objects/client_object.rb +55 -0
  12. data/lib/ridley/chef_objects/cookbook_object.rb +190 -0
  13. data/lib/ridley/chef_objects/data_bag_item_obect.rb +104 -0
  14. data/lib/ridley/chef_objects/data_bag_object.rb +31 -0
  15. data/lib/ridley/chef_objects/environment_object.rb +59 -0
  16. data/lib/ridley/chef_objects/node_object.rb +161 -0
  17. data/lib/ridley/chef_objects/role_object.rb +62 -0
  18. data/lib/ridley/chef_objects/sandbox_object.rb +58 -0
  19. data/lib/ridley/client.rb +76 -45
  20. data/lib/ridley/connection.rb +1 -1
  21. data/lib/ridley/errors.rb +8 -1
  22. data/lib/ridley/host_connector.rb +26 -6
  23. data/lib/ridley/host_connector/ssh.rb +3 -3
  24. data/lib/ridley/host_connector/ssh/worker.rb +7 -9
  25. data/lib/ridley/host_connector/winrm/worker.rb +4 -5
  26. data/lib/ridley/mixin/bootstrap_binding.rb +1 -12
  27. data/lib/ridley/resource.rb +51 -171
  28. data/lib/ridley/resources/client_resource.rb +18 -68
  29. data/lib/ridley/resources/cookbook_resource.rb +181 -381
  30. data/lib/ridley/resources/data_bag_item_resource.rb +55 -161
  31. data/lib/ridley/resources/data_bag_resource.rb +20 -61
  32. data/lib/ridley/resources/environment_resource.rb +9 -64
  33. data/lib/ridley/resources/node_resource.rb +135 -311
  34. data/lib/ridley/resources/role_resource.rb +1 -57
  35. data/lib/ridley/resources/sandbox_resource.rb +80 -65
  36. data/lib/ridley/resources/search_resource.rb +99 -0
  37. data/lib/ridley/sandbox_uploader.rb +12 -52
  38. data/lib/ridley/version.rb +1 -1
  39. data/spec/acceptance/bootstrapping_spec.rb +1 -1
  40. data/spec/acceptance/client_resource_spec.rb +15 -37
  41. data/spec/acceptance/data_bag_item_resource_spec.rb +8 -14
  42. data/spec/acceptance/data_bag_resource_spec.rb +1 -1
  43. data/spec/acceptance/environment_resource_spec.rb +13 -22
  44. data/spec/acceptance/node_resource_spec.rb +10 -29
  45. data/spec/acceptance/role_resource_spec.rb +14 -13
  46. data/spec/acceptance/sandbox_resource_spec.rb +2 -2
  47. data/spec/support/shared_examples/ridley_resource.rb +2 -23
  48. data/spec/unit/ridley/bootstrap_bindings/unix_template_binding_spec.rb +3 -4
  49. data/spec/unit/ridley/bootstrap_bindings/windows_template_binding_spec.rb +3 -5
  50. data/spec/unit/ridley/bootstrapper/context_spec.rb +2 -3
  51. data/spec/unit/ridley/bootstrapper_spec.rb +1 -1
  52. data/spec/unit/ridley/chef_object_spec.rb +240 -0
  53. data/spec/unit/ridley/chef_objects/client_object_spec.rb +11 -0
  54. data/spec/unit/ridley/chef_objects/cookbook_object_spec.rb +93 -0
  55. data/spec/unit/ridley/chef_objects/data_bag_item_object_spec.rb +74 -0
  56. data/spec/unit/ridley/chef_objects/data_bag_object_spec.rb +9 -0
  57. data/spec/unit/ridley/chef_objects/environment_object_spec.rb +57 -0
  58. data/spec/unit/ridley/chef_objects/node_object_spec.rb +252 -0
  59. data/spec/unit/ridley/chef_objects/role_object_spec.rb +57 -0
  60. data/spec/unit/ridley/chef_objects/sandbox_object_spec.rb +66 -0
  61. data/spec/unit/ridley/client_spec.rb +51 -51
  62. data/spec/unit/ridley/host_connector/ssh/worker_spec.rb +4 -4
  63. data/spec/unit/ridley/host_connector/ssh_spec.rb +26 -24
  64. data/spec/unit/ridley/host_connector/winrm/worker_spec.rb +3 -4
  65. data/spec/unit/ridley/host_connector/winrm_spec.rb +4 -4
  66. data/spec/unit/ridley/host_connector_spec.rb +40 -3
  67. data/spec/unit/ridley/mixin/bootstrap_binding_spec.rb +1 -1
  68. data/spec/unit/ridley/resource_spec.rb +81 -109
  69. data/spec/unit/ridley/resources/client_resource_spec.rb +18 -33
  70. data/spec/unit/ridley/resources/cookbook_resource_spec.rb +56 -230
  71. data/spec/unit/ridley/resources/data_bag_item_resource_spec.rb +2 -57
  72. data/spec/unit/ridley/resources/data_bag_resource_spec.rb +12 -7
  73. data/spec/unit/ridley/resources/environment_resource_spec.rb +10 -118
  74. data/spec/unit/ridley/resources/node_resource_spec.rb +83 -394
  75. data/spec/unit/ridley/resources/role_resource_spec.rb +2 -56
  76. data/spec/unit/ridley/resources/sandbox_resource_spec.rb +139 -136
  77. data/spec/unit/ridley/resources/search_resource_spec.rb +234 -0
  78. data/spec/unit/ridley/sandbox_uploader_spec.rb +13 -58
  79. metadata +36 -17
  80. data/lib/ridley/chef/chefignore.rb +0 -76
  81. data/lib/ridley/resources/encrypted_data_bag_item_resource.rb +0 -55
  82. data/lib/ridley/resources/search.rb +0 -101
  83. data/spec/fixtures/chefignore +0 -8
  84. data/spec/unit/ridley/chef/chefignore_spec.rb +0 -40
  85. data/spec/unit/ridley/resources/search_spec.rb +0 -221
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ridley::SandboxObject do
4
+ let(:resource) { double('chef-resource') }
5
+
6
+ subject do
7
+ described_class.new(double('registry'),
8
+ "uri" => "https://api.opscode.com/organizations/vialstudios/sandboxes/bd091b150b0a4578b97771af6abf3e05",
9
+ "checksums" => {
10
+ "385ea5490c86570c7de71070bce9384a" => {
11
+ "url" => "https://s3.amazonaws.com/opscode-platform-production-data/organization",
12
+ "needs_upload" => true
13
+ },
14
+ "f6f73175e979bd90af6184ec277f760c" => {
15
+ "url" => "https://s3.amazonaws.com/opscode-platform-production-data/organization",
16
+ "needs_upload" => true
17
+ },
18
+ "2e03dd7e5b2e6c8eab1cf41ac61396d5" => {
19
+ "url" => "https://s3.amazonaws.com/opscode-platform-production-data/organization",
20
+ "needs_upload" => true
21
+ },
22
+ },
23
+ "sandbox_id" => "bd091b150b0a4578b97771af6abf3e05"
24
+ )
25
+ end
26
+
27
+ before { subject.stub(resource: resource) }
28
+
29
+ describe "#checksums" do
30
+ pending
31
+ end
32
+
33
+ describe "#commit" do
34
+ let(:response) { { is_completed: nil} }
35
+ before { resource.should_receive(:commit).with(subject).and_return(response) }
36
+
37
+ context "when the commit is successful" do
38
+ before { response[:is_completed] = true }
39
+
40
+ it "has an 'is_completed' value of true" do
41
+ subject.commit
42
+
43
+ subject.is_completed.should be_true
44
+ end
45
+ end
46
+
47
+ context "when the commit is a failure" do
48
+ before { response[:is_completed] = false }
49
+
50
+ it "has an 'is_completed' value of false" do
51
+ subject.commit
52
+
53
+ subject.is_completed.should be_false
54
+ end
55
+ end
56
+ end
57
+
58
+ describe "#upload" do
59
+ it "delegates to resource#upload" do
60
+ checksums = double('checksums')
61
+ resource.should_receive(:upload).with(subject, checksums)
62
+
63
+ subject.upload(checksums)
64
+ end
65
+ end
66
+ end
@@ -24,124 +24,124 @@ describe Ridley::Client do
24
24
  end
25
25
 
26
26
  describe "ClassMethods" do
27
- subject { described_class }
27
+ let(:options) do
28
+ {
29
+ server_url: "https://api.opscode.com/some_path",
30
+ client_name: client_name,
31
+ client_key: client_key
32
+ }
33
+ end
28
34
 
29
35
  describe "::initialize" do
30
- let(:server_url) { "https://api.opscode.com/some_path" }
36
+ subject { described_class.new(options) }
31
37
 
32
38
  describe "parsing the 'server_url' option" do
33
- before(:each) do
34
- @conn = subject.new(
35
- server_url: server_url,
36
- client_name: client_name,
37
- client_key: client_key,
38
- )
39
- end
40
-
41
- it "assigns a 'host' attribute from the given 'server_url' option" do
42
- @conn.host.should eql("api.opscode.com")
43
- end
44
-
45
- it "assigns a 'scheme' attribute from the given 'server_url' option" do
46
- @conn.scheme.should eql("https")
47
- end
48
-
49
- it "sets a 'path_prefix' to the root of the given 'server_url' option" do
50
- @conn.path_prefix.should eql("/")
51
- end
39
+ its(:host) { should eql("api.opscode.com") }
40
+ its(:scheme) { should eql("https") }
41
+ its(:path_prefix) { should eql("/") }
52
42
  end
53
43
 
54
44
  describe "with a server_url containing an organization" do
55
- before(:each) do
56
- @conn = subject.new(
57
- server_url: "#{server_url}/organizations/#{organization}",
58
- client_name: client_name,
59
- client_key: client_key
60
- )
45
+ before do
46
+ options[:server_url] = "#{server_url}/organizations/#{organization}"
61
47
  end
62
48
 
63
49
  it "gets the host data from the server_url" do
64
- @conn.host.should eql("api.opscode.com")
65
- @conn.scheme.should eql("https")
50
+ subject.host.should eql("api.opscode.com")
51
+ subject.scheme.should eql("https")
66
52
  end
67
53
 
68
- it "assigns the value of the 'organization' option to an 'organization' attribute" do
69
- @conn.connection.organization.should eql(organization)
54
+ it "takes the organization out of the server_url and assigns it to the organization reader" do
55
+ subject.organization.should eql(organization)
70
56
  end
71
57
 
72
58
  it "sets the 'path_prefix' of the connection the organization sub URI" do
73
- @conn.path_prefix.should eql("/organizations/#{organization}")
59
+ subject.path_prefix.should eql("/organizations/#{organization}")
74
60
  end
75
61
  end
76
62
 
77
63
  it "raises 'ArgumentError' if a value for server_url is not given" do
78
- lambda {
79
- subject.new(
64
+ expect {
65
+ described_class.new(
80
66
  client_name: client_name,
81
67
  client_key: client_key
82
68
  )
83
- }.should raise_error(ArgumentError, "Missing required option(s): 'server_url'")
69
+ }.to raise_error(ArgumentError, "Missing required option(s): 'server_url'")
84
70
  end
85
71
 
86
72
  it "raises if a value for client_name is not given" do
87
- lambda {
88
- subject.new(
73
+ expect {
74
+ described_class.new(
89
75
  server_url: server_url,
90
76
  client_key: client_key
91
77
  )
92
- }.should raise_error(ArgumentError, "Missing required option(s): 'client_name'")
78
+ }.to raise_error(ArgumentError, "Missing required option(s): 'client_name'")
93
79
  end
94
80
 
95
81
  it "raises if a value for client_key is not given" do
96
- lambda {
97
- subject.new(
82
+ expect {
83
+ described_class.new(
98
84
  server_url: server_url,
99
85
  client_name: client_name
100
86
  )
101
- }.should raise_error(ArgumentError, "Missing required option(s): 'client_key'")
87
+ }.to raise_error(ArgumentError, "Missing required option(s): 'client_key'")
102
88
  end
103
89
 
104
90
  it "raises a ClientKeyFileNotFound if the filepath for client_key is not found" do
105
91
  config[:client_key] = "/tmp/nofile.xxsa"
106
92
 
107
- lambda {
108
- subject.new(config)
109
- }.should raise_error(Ridley::Errors::ClientKeyFileNotFound)
93
+ expect {
94
+ described_class.new(config)
95
+ }.to raise_error(Ridley::Errors::ClientKeyFileNotFound)
110
96
  end
111
97
 
112
98
  it "expands the path of the client_key" do
113
99
  config[:client_key] = "~/"
114
100
 
115
- subject.new(config).client_key.should_not == "~/"
101
+ described_class.new(config).client_key.should_not == "~/"
116
102
  end
117
103
 
118
104
  it "assigns a 'ssh' attribute from the given 'ssh' option" do
119
- subject.new(config).ssh.should eql({user: "reset", password: "password1", port: "222"})
105
+ described_class.new(config).ssh.should eql({user: "reset", password: "password1", port: "222"})
120
106
  end
121
107
 
122
108
  it "assigns a 'winrm' attribute from the given 'winrm' option" do
123
- subject.new(config).winrm.should eql({user: "reset", password: "password2", port: "5986"})
109
+ described_class.new(config).winrm.should eql({user: "reset", password: "password2", port: "5986"})
124
110
  end
125
111
 
126
112
  it "assigns a 'chef_version' attribute from the given 'chef_version' option" do
127
- subject.new(config).chef_version.should eql("10.24.0-01")
113
+ described_class.new(config).chef_version.should eql("10.24.0-01")
128
114
  end
129
115
  end
130
116
 
131
117
  describe "::open" do
132
118
  it "raises a LocalJumpError if no block is given" do
133
119
  lambda {
134
- subject.open(config)
120
+ described_class.open(config)
135
121
  }.should raise_error(LocalJumpError)
136
122
  end
137
123
  end
138
124
  end
139
125
 
140
- subject do
141
- described_class.new(config)
126
+ let(:instance) { described_class.new(config) }
127
+
128
+ describe "#node" do
129
+ subject { instance.node }
130
+
131
+ it { should be_a(Ridley::NodeResource) }
132
+
133
+ its(:server_url) { should eql(config[:server_url]) }
134
+ its(:validator_path) { should eql(config[:validator_path]) }
135
+ its(:validator_client) { should eql(config[:validator_client]) }
136
+ its(:encrypted_data_bag_secret) { should eql(instance.encrypted_data_bag_secret) }
137
+ its(:ssh) { should eql(config[:ssh]) }
138
+ its(:winrm) { should eql(config[:winrm]) }
139
+ its(:chef_version) { should eql(config[:chef_version]) }
142
140
  end
143
141
 
144
142
  describe "#encrypted_data_bag_secret" do
143
+ subject { instance }
144
+
145
145
  it "returns a string" do
146
146
  subject.encrypted_data_bag_secret.should be_a(String)
147
147
  end
@@ -6,7 +6,7 @@ describe Ridley::HostConnector::SSH::Worker do
6
6
 
7
7
  let(:host) { 'reset.riotgames.com' }
8
8
  let(:options) { {} }
9
-
9
+
10
10
  describe "#sudo" do
11
11
  subject { ssh_worker.sudo }
12
12
 
@@ -14,7 +14,7 @@ describe Ridley::HostConnector::SSH::Worker do
14
14
 
15
15
  context "with sudo" do
16
16
  let(:options) { { ssh: { sudo: true } } }
17
-
17
+
18
18
  it { should be_true }
19
19
  end
20
20
  end
@@ -35,10 +35,10 @@ describe Ridley::HostConnector::SSH::Worker do
35
35
  end
36
36
 
37
37
  describe "#put_secret" do
38
- subject(:put_secret) { ssh_worker.put_secret(encrypted_data_bag_secret_path) }
38
+ subject(:put_secret) { ssh_worker.put_secret(secret) }
39
39
  let(:encrypted_data_bag_secret_path) { fixtures_path.join("encrypted_data_bag_secret").to_s }
40
40
  let(:secret) { File.read(encrypted_data_bag_secret_path).chomp }
41
-
41
+
42
42
  it "receives a run command with echo" do
43
43
  ssh_worker.should_receive(:run).with("echo '#{secret}' > /etc/chef/encrypted_data_bag_secret; chmod 0600 /etc/chef/encrypted_data_bag_secret")
44
44
  put_secret
@@ -1,37 +1,29 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Ridley::HostConnector::SSH do
4
- let(:connection) { double('conn', ssh: { user: "vagrant", password: "vagrant" }) }
4
+ let(:resource) { double('resource') }
5
5
 
6
6
  let(:node_one) do
7
- Ridley::NodeResource.new(connection, automatic: { cloud: { public_hostname: "33.33.33.10" } })
7
+ Ridley::NodeObject.new(resource, automatic: { cloud: { public_hostname: "33.33.33.10" } })
8
8
  end
9
9
 
10
10
  let(:node_two) do
11
- Ridley::NodeResource.new(connection, automatic: { cloud: { public_hostname: "33.33.33.11" } })
11
+ Ridley::NodeObject.new(resource, automatic: { cloud: { public_hostname: "33.33.33.11" } })
12
12
  end
13
13
 
14
- describe "ClassMethods" do
15
- subject { Ridley::HostConnector::SSH }
16
-
17
- describe "::start" do
18
- let(:options) do
19
- {
20
- user: "vagrant",
21
- password: "vagrant",
22
- timeout: 1
23
- }
24
- end
25
-
26
- it "evaluates within the context of a new SSH and returns the last item in the block" do
27
- result = subject.start([node_one, node_two], options) do |ssh|
28
- ssh.run("ls")
29
- end
14
+ let(:options) do
15
+ {
16
+ user: "vagrant",
17
+ password: "vagrant",
18
+ timeout: 1
19
+ }
20
+ end
30
21
 
31
- result.should be_a(Ridley::HostConnector::ResponseSet)
32
- end
22
+ describe "ClassMethods" do
23
+ subject { described_class }
33
24
 
34
- it "raises a LocalJumpError if a block is not provided" do
25
+ describe "::start" do
26
+ it "raises a LocalJumpError if a block is not provided" do
35
27
  expect {
36
28
  subject.start([node_one, node_two], options)
37
29
  }.to raise_error(LocalJumpError)
@@ -39,10 +31,20 @@ describe Ridley::HostConnector::SSH do
39
31
  end
40
32
  end
41
33
 
42
- subject { Ridley::HostConnector::SSH.new([node_one, node_two], ssh: {user: "vagrant", password: "vagrant", timeout: 1}) }
34
+ subject do
35
+ Ridley::HostConnector::SSH.new([node_one, node_two], ssh: { user: "vagrant", password: "vagrant", timeout: 1 })
36
+ end
43
37
 
44
38
  describe "#run" do
45
- it "returns an HostConnector::ResponseSet" do
39
+ let(:worker) { double('worker', terminate: nil) }
40
+ let(:response) { Ridley::HostConnector::Response.new("host") }
41
+ before { Ridley::HostConnector::SSH::Worker.stub(:new).and_return(worker) }
42
+
43
+ before do
44
+ worker.stub_chain(:future, :run).and_return(double(value: [:ok, response]))
45
+ end
46
+
47
+ it "returns an SSH::ResponseSet" do
46
48
  subject.run("ls").should be_a(Ridley::HostConnector::ResponseSet)
47
49
  end
48
50
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Ridley::HostConnector::WinRM::Worker do
4
4
  subject { winrm_worker }
5
5
  let(:winrm_worker) { described_class.new(host, options) }
6
- let(:host) { 'reset.riotgames.com' }
6
+ let(:host) { 'reset.riotgames.com' }
7
7
  let(:options) { {} }
8
8
 
9
9
  before do
@@ -35,7 +35,7 @@ describe Ridley::HostConnector::WinRM::Worker do
35
35
  let(:command_uploader_stub) { double('CommandUploader') }
36
36
 
37
37
  it { should eq(command) }
38
-
38
+
39
39
  context "when a command is more than 2047 characters" do
40
40
  let(:command) { "a" * 2048 }
41
41
 
@@ -62,8 +62,7 @@ describe Ridley::HostConnector::WinRM::Worker do
62
62
  end
63
63
 
64
64
  describe "#put_secret" do
65
- subject(:put_secret) { winrm_worker.put_secret(encrypted_data_bag_secret_path) }
66
-
65
+ subject(:put_secret) { winrm_worker.put_secret(secret) }
67
66
  let(:encrypted_data_bag_secret_path) { fixtures_path.join("encrypted_data_bag_secret").to_s }
68
67
  let(:secret) { File.read(encrypted_data_bag_secret_path).chomp }
69
68
 
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Ridley::HostConnector::WinRM do
4
- let(:connection) { double('conn', ssh: { user: "vagrant", password: "vagrant" }) }
4
+ let(:resource) { double('resource') }
5
5
 
6
6
  let(:node_one) do
7
- Ridley::NodeResource.new(connection, automatic: { cloud: { public_hostname: "33.33.33.10" } })
7
+ Ridley::NodeObject.new(resource, automatic: { cloud: { public_hostname: "33.33.33.10" } })
8
8
  end
9
9
 
10
10
  let(:node_two) do
11
- Ridley::NodeResource.new(connection, automatic: { cloud: { public_hostname: "33.33.33.11" } })
11
+ Ridley::NodeObject.new(resource, automatic: { cloud: { public_hostname: "33.33.33.11" } })
12
12
  end
13
13
  let(:command_uploader) { double('command_uploader', cleanup: nil) }
14
14
 
@@ -18,7 +18,7 @@ describe Ridley::HostConnector::WinRM do
18
18
 
19
19
  describe "ClassMethods" do
20
20
  subject { Ridley::HostConnector::WinRM }
21
-
21
+
22
22
  describe "::start" do
23
23
  let(:options) do
24
24
  {
@@ -13,7 +13,44 @@ describe Ridley::HostConnector do
13
13
  described_class::DEFAULT_WINRM_PORT.should eq(5985)
14
14
  end
15
15
 
16
- describe "#connector_port_open?" do
16
+ describe "::new" do
17
+ let(:host) { "192.168.1.1" }
18
+ let(:options) do
19
+ {
20
+ ssh: {
21
+ user: "reset",
22
+ password: "lol"
23
+ },
24
+ winrm: {
25
+ user: "Administrator",
26
+ password: "secret"
27
+ }
28
+ }
29
+ end
30
+
31
+ subject { described_class.new(host, options) }
32
+
33
+ context "when the best connector is SSH" do
34
+ before do
35
+ described_class.stub(:best_connector_for).and_yield(Ridley::HostConnector::SSH)
36
+ end
37
+
38
+ it { should be_a(Ridley::HostConnector::SSH::Worker) }
39
+ its(:user) { should eq("reset") }
40
+ end
41
+
42
+ context "when the best connector is WinRM" do
43
+ before do
44
+ described_class.stub(:best_connector_for).and_yield(Ridley::HostConnector::WinRM)
45
+ end
46
+
47
+ it { should be_a(Ridley::HostConnector::WinRM::Worker) }
48
+ its(:user) { should eq("Administrator") }
49
+ its(:password) { should eq("secret") }
50
+ end
51
+ end
52
+
53
+ describe "::connector_port_open?" do
17
54
  let(:host) { "127.0.0.1" }
18
55
  let(:port) { 22 }
19
56
  let(:socket) { double(:new => true, :close => nil) }
@@ -54,7 +91,7 @@ describe Ridley::HostConnector do
54
91
  end
55
92
  end
56
93
 
57
- describe "#best_connector_for" do
94
+ describe "::best_connector_for" do
58
95
  let(:host) {"127.0.0.1"}
59
96
 
60
97
  context "when an SSH port is open" do
@@ -90,7 +127,7 @@ describe Ridley::HostConnector do
90
127
  end
91
128
  end
92
129
 
93
- describe "#parse_port_options" do
130
+ describe "::parse_port_options" do
94
131
  let(:options) do
95
132
  {
96
133
  ssh: {