kitchen-rackspace 0.21.2 → 0.22.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.
@@ -0,0 +1,162 @@
1
+ RSpec.describe Kitchen::Driver::Rackspace do
2
+ include_context "a driver for an instance"
3
+
4
+ describe "#compute" do
5
+ let(:config) do
6
+ {
7
+ rackspace_username: "monkey",
8
+ rackspace_api_key: "potato",
9
+ rackspace_region: "ord",
10
+ }
11
+ end
12
+
13
+ context "all requirements provided" do
14
+ it "creates a new compute connection" do
15
+ allow(Fog::Compute).to receive(:new) { |arg| arg }
16
+
17
+ expect(driver.send(:compute)).to eq(
18
+ provider: "Rackspace",
19
+ version: "v2",
20
+ rackspace_username: "monkey",
21
+ rackspace_api_key: "potato",
22
+ rackspace_region: "ord"
23
+ )
24
+ end
25
+ end
26
+
27
+ context "no username provided" do
28
+ let(:config) { { rackspace_username: nil, rackspace_api_key: "1234" } }
29
+
30
+ it "raises an error" do
31
+ expect { driver.send(:compute) }.to raise_error(ArgumentError)
32
+ end
33
+ end
34
+
35
+ context "no API key provided" do
36
+ let(:config) { { rackspace_username: "monkey", rackspace_api_key: nil } }
37
+
38
+ it "raises an error" do
39
+ expect { driver.send(:compute) }.to raise_error(ArgumentError)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#create_server" do
45
+ subject(:server_def) { driver.send(:create_server) }
46
+
47
+ let(:config) do
48
+ {
49
+ server_name: "hello",
50
+ image_id: "there",
51
+ flavor_id: "captain",
52
+ public_key_path: "tarpals",
53
+ no_passwd_lock: false,
54
+ networks: nil,
55
+ user_data: nil,
56
+ config_drive: false,
57
+ }
58
+ end
59
+ let(:servers) do
60
+ collection = instance_double(Fog::Compute::RackspaceV2::Servers)
61
+ allow(collection).to receive(:bootstrap) { |arg| arg }
62
+ collection
63
+ end
64
+
65
+ before do
66
+ allow_any_instance_of(described_class).to receive(:compute)
67
+ .and_return(double("compute", servers:))
68
+ end
69
+
70
+ it "creates the server using a compute connection" do
71
+ expect(server_def).to eq(
72
+ name: "hello",
73
+ image_id: "there",
74
+ flavor_id: "captain",
75
+ public_key_path: "tarpals",
76
+ no_passwd_lock: false,
77
+ networks: nil,
78
+ user_data: nil,
79
+ config_drive: false
80
+ )
81
+ end
82
+
83
+ it "passes user data through" do
84
+ config[:user_data] = "#cloud-config\n"
85
+
86
+ expect(server_def[:user_data]).to eq("#cloud-config\n")
87
+ end
88
+
89
+ it "passes the config drive setting through" do
90
+ config[:config_drive] = true
91
+
92
+ expect(server_def[:config_drive]).to eq(true)
93
+ end
94
+
95
+ # RackConnect and Managed Service Level both need to log in as root to do
96
+ # their work, so the driver overrides no_passwd_lock whenever either wait
97
+ # is requested. Getting this wrong leaves the automation stuck forever.
98
+ context "rackconnect_wait is set" do
99
+ before { config[:rackconnect_wait] = true }
100
+
101
+ it "unlocks the root password even though the option says otherwise" do
102
+ expect(server_def[:no_passwd_lock]).to eq(true)
103
+ end
104
+ end
105
+
106
+ context "servicelevel_wait is set" do
107
+ before { config[:servicelevel_wait] = true }
108
+
109
+ it "unlocks the root password even though the option says otherwise" do
110
+ expect(server_def[:no_passwd_lock]).to eq(true)
111
+ end
112
+ end
113
+
114
+ context "neither wait is set" do
115
+ it "leaves the configured no_passwd_lock alone" do
116
+ config[:no_passwd_lock] = true
117
+
118
+ expect(server_def[:no_passwd_lock]).to eq(true)
119
+ end
120
+
121
+ it "leaves a false no_passwd_lock alone" do
122
+ expect(server_def[:no_passwd_lock]).to eq(false)
123
+ end
124
+ end
125
+
126
+ context "additional networks specified" do
127
+ before { config[:networks] = %w{bob_dole} }
128
+
129
+ it "keeps PublicNet and ServiceNet ahead of the custom network" do
130
+ expect(server_def[:networks]).to eq(
131
+ %w{
132
+ 00000000-0000-0000-0000-000000000000
133
+ 11111111-1111-1111-1111-111111111111
134
+ bob_dole
135
+ }
136
+ )
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "#networks" do
142
+ context "the default Rackspace networks" do
143
+ it "returns nil so Fog will use the defaults" do
144
+ expect(driver.send(:networks)).to be_nil
145
+ end
146
+ end
147
+
148
+ context "a custom Rackspace network" do
149
+ let(:config) { { networks: %w{abcdefg} } }
150
+
151
+ it "returns the base networks plus the custom one" do
152
+ expect(driver.send(:networks)).to eq(
153
+ %w{
154
+ 00000000-0000-0000-0000-000000000000
155
+ 11111111-1111-1111-1111-111111111111
156
+ abcdefg
157
+ }
158
+ )
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,210 @@
1
+ RSpec.describe Kitchen::Driver::Rackspace do
2
+ include_context "a driver for an instance"
3
+
4
+ before do
5
+ allow(Fog).to receive(:timeout=)
6
+ end
7
+
8
+ describe "default options" do
9
+ it "defaults to v2 cloud" do
10
+ expect(driver[:version]).to eq("v2")
11
+ end
12
+
13
+ it "defaults to a 2 GB general purpose flavor" do
14
+ expect(driver[:flavor_id]).to eq("general1-2")
15
+ end
16
+
17
+ it "defaults to local user's SSH public key" do
18
+ expect(File).to receive(:exist?).with(%r{/.ssh/id_rsa.pub$})
19
+ .and_return(true)
20
+
21
+ expect(driver[:public_key_path]).to match(%r{/.ssh/id_rsa.pub$})
22
+ end
23
+
24
+ it "falls through the other key types when there is no RSA key" do
25
+ allow(File).to receive(:exist?).and_return(false)
26
+ allow(File).to receive(:exist?).with(%r{/.ssh/id_ecdsa.pub$})
27
+ .and_return(true)
28
+
29
+ expect(driver[:public_key_path]).to match(%r{/.ssh/id_ecdsa.pub$})
30
+ end
31
+
32
+ it "leaves public_key_path unset when ~/.ssh holds no public key" do
33
+ allow(File).to receive(:exist?).and_return(false)
34
+
35
+ expect(driver[:public_key_path]).to be_nil
36
+ end
37
+
38
+ it "defaults to SSH with root user on port 22" do
39
+ expect(driver[:username]).to eq("root")
40
+ expect(driver[:port]).to eq("22")
41
+ end
42
+
43
+ it "defaults to a random server name" do
44
+ expect(driver[:server_name]).to match(/^potatoes-/)
45
+ end
46
+
47
+ it "defaults to the DFW region" do
48
+ expect(driver[:rackspace_region]).to eq("dfw")
49
+ end
50
+
51
+ it "defaults to username from $RACKSPACE_USERNAME" do
52
+ expect(driver[:rackspace_username]).to eq("user")
53
+ end
54
+
55
+ it "defaults to API key from $RACKSPACE_API_KEY" do
56
+ expect(driver[:rackspace_api_key]).to eq("key")
57
+ end
58
+
59
+ it "defaults to wait_for timeout of 600 seconds" do
60
+ expect(driver[:wait_for]).to eq(600)
61
+ end
62
+
63
+ it "defaults the SSH TCP bypassing to false" do
64
+ expect(driver[:no_ssh_tcp_check]).to eq(false)
65
+ end
66
+
67
+ it "defaults the TCP bypass sleep time to 120 seconds" do
68
+ expect(driver[:no_ssh_tcp_check_sleep]).to eq(120)
69
+ end
70
+
71
+ it "defaults to the standard Rackspace networks" do
72
+ expect(driver[:networks]).to eq(nil)
73
+ end
74
+
75
+ it "defaults to not waiting for rackconnect" do
76
+ expect(driver[:rackconnect_wait]).to eq(false)
77
+ end
78
+
79
+ it "defaults to not waiting for managed service level" do
80
+ expect(driver[:servicelevel_wait]).to eq(false)
81
+ end
82
+
83
+ it "defaults to the public ip address" do
84
+ expect(driver[:servicenet]).to eq(false)
85
+ end
86
+
87
+ it "defaults to no_passwd_lock as false" do
88
+ expect(driver[:no_passwd_lock]).to eq(false)
89
+ end
90
+
91
+ it "defaults to attaching the config drive" do
92
+ expect(driver[:config_drive]).to eq(true)
93
+ end
94
+
95
+ it "defaults to sending no user data" do
96
+ expect(driver[:user_data]).to eq(nil)
97
+ end
98
+ end
99
+
100
+ describe "overridden options" do
101
+ overrides = {
102
+ version: "v3",
103
+ image_id: "22",
104
+ flavor_id: "33",
105
+ public_key_path: "/tmp",
106
+ username: "admin",
107
+ port: "2222",
108
+ server_name: "puppy",
109
+ rackspace_region: "ord",
110
+ wait_for: 1200,
111
+ rackconnect_wait: true,
112
+ servicelevel_wait: true,
113
+ servicenet: true,
114
+ no_passwd_lock: true,
115
+ no_ssh_tcp_check: true,
116
+ no_ssh_tcp_check_sleep: 180,
117
+ config_drive: false,
118
+ user_data: "#cloud-config\n",
119
+ networks: %w{abcdefg},
120
+ }
121
+ let(:config) { overrides.dup }
122
+
123
+ # Every key here is a real `default_config` on the driver. Asserting that
124
+ # first keeps this list from drifting into options that do not exist --
125
+ # `driver[:whatever]` happily echoes back anything you put in the config
126
+ # hash, so a test for a made-up option passes without proving anything.
127
+ it "only names options the driver actually declares" do
128
+ expect(described_class.defaults.keys).to include(*overrides.keys)
129
+ end
130
+
131
+ overrides.each do |key, value|
132
+ it "uses the overridden #{key} option" do
133
+ expect(driver[key]).to eq(value)
134
+ end
135
+ end
136
+ end
137
+
138
+ describe "credentials from the environment" do
139
+ it "reads the username from $RACKSPACE_USERNAME" do
140
+ ENV["RACKSPACE_USERNAME"] = "rax_user"
141
+
142
+ expect(driver[:rackspace_username]).to eq("rax_user")
143
+ end
144
+
145
+ it "falls back to $OS_USERNAME" do
146
+ ENV.delete("RACKSPACE_USERNAME")
147
+ ENV["OS_USERNAME"] = "os_user"
148
+
149
+ expect(driver[:rackspace_username]).to eq("os_user")
150
+ end
151
+
152
+ it "prefers $RACKSPACE_USERNAME over $OS_USERNAME" do
153
+ ENV["RACKSPACE_USERNAME"] = "rax_user"
154
+ ENV["OS_USERNAME"] = "os_user"
155
+
156
+ expect(driver[:rackspace_username]).to eq("rax_user")
157
+ end
158
+
159
+ it "falls back to $OS_PASSWORD for the API key" do
160
+ ENV.delete("RACKSPACE_API_KEY")
161
+ ENV["OS_PASSWORD"] = "os_pass"
162
+
163
+ expect(driver[:rackspace_api_key]).to eq("os_pass")
164
+ end
165
+
166
+ it "prefers $RACKSPACE_API_KEY over $OS_PASSWORD" do
167
+ ENV["RACKSPACE_API_KEY"] = "rax_key"
168
+ ENV["OS_PASSWORD"] = "os_pass"
169
+
170
+ expect(driver[:rackspace_api_key]).to eq("rax_key")
171
+ end
172
+
173
+ it "falls back to $OS_REGION_NAME for the region" do
174
+ ENV["OS_REGION_NAME"] = "os_region"
175
+
176
+ expect(driver[:rackspace_region]).to eq("os_region")
177
+ end
178
+
179
+ it "prefers $RACKSPACE_REGION over $OS_REGION_NAME" do
180
+ ENV["RACKSPACE_REGION"] = "ord"
181
+ ENV["OS_REGION_NAME"] = "os_region"
182
+
183
+ expect(driver[:rackspace_region]).to eq("ord")
184
+ end
185
+
186
+ it "falls back to dfw when neither region variable is set" do
187
+ expect(driver[:rackspace_region]).to eq("dfw")
188
+ end
189
+ end
190
+
191
+ describe "the fog timeout" do
192
+ it "hands wait_for to fog as its global timeout" do
193
+ described_class.new(wait_for: 1200)
194
+
195
+ expect(Fog).to have_received(:timeout=).with(1200)
196
+ end
197
+
198
+ it "uses the wait_for default when the option is not set" do
199
+ described_class.new({})
200
+
201
+ expect(Fog).to have_received(:timeout=).with(600)
202
+ end
203
+
204
+ it "coerces a wait_for given as a string" do
205
+ described_class.new(wait_for: "1200")
206
+
207
+ expect(Fog).to have_received(:timeout=).with(1200)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,150 @@
1
+ RSpec.describe Kitchen::Driver::Rackspace do
2
+ include_context "a driver for an instance"
3
+ include_context "a built server"
4
+
5
+ let(:credentials) do
6
+ { rackspace_username: "hello", rackspace_api_key: "world", wait_for: 1200 }
7
+ end
8
+ let(:config) { credentials }
9
+
10
+ describe "#create" do
11
+ it "generates a server name in the absence of one" do
12
+ driver.create(state)
13
+
14
+ expect(driver[:server_name]).to eq("a_monkey!")
15
+ end
16
+
17
+ it "records the server ID" do
18
+ driver.create(state)
19
+
20
+ expect(state[:server_id]).to eq("test123")
21
+ end
22
+
23
+ it "records the public IP as the hostname" do
24
+ driver.create(state)
25
+
26
+ expect(state[:hostname]).to eq("1.2.3.4")
27
+ end
28
+
29
+ it "records the login user" do
30
+ driver.create(state)
31
+
32
+ expect(state[:username]).to eq("root")
33
+ end
34
+
35
+ it "records the SSH port" do
36
+ driver.create(state)
37
+
38
+ expect(state[:port]).to eq("22")
39
+ end
40
+
41
+ it "records an overridden login user and port" do
42
+ config.merge!(username: "admin", port: "2222")
43
+ driver.create(state)
44
+
45
+ expect(state).to include(username: "admin", port: "2222")
46
+ end
47
+
48
+ it "waits for the server to report ready" do
49
+ expect(server).to receive(:wait_for)
50
+
51
+ driver.create(state)
52
+ end
53
+
54
+ it "waits for the instance to accept a login" do
55
+ expect(driver).to receive(:tcp_check).with(state)
56
+
57
+ driver.create(state)
58
+ end
59
+
60
+ it "does not wait for rackconnect by default" do
61
+ expect(driver).to_not receive(:rackconnect_check)
62
+
63
+ driver.create(state)
64
+ end
65
+
66
+ it "does not wait for managed service level by default" do
67
+ expect(driver).to_not receive(:servicelevel_check)
68
+
69
+ driver.create(state)
70
+ end
71
+
72
+ context "a Fog error" do
73
+ before do
74
+ allow_any_instance_of(described_class).to receive(:create_server)
75
+ .and_raise(Fog::Errors::Error, "Uhoh")
76
+ end
77
+
78
+ it "re-raises it as a Kitchen failure" do
79
+ expect { driver.create(state) }
80
+ .to raise_error(Kitchen::ActionFailed, "Uhoh")
81
+ end
82
+ end
83
+
84
+ # Anything the Rackspace API rejects surfaces from Excon rather than fog,
85
+ # and it has to be turned into a Kitchen failure too or the user gets a
86
+ # raw stack trace instead of a message.
87
+ context "an Excon error" do
88
+ before do
89
+ allow_any_instance_of(described_class).to receive(:create_server)
90
+ .and_raise(Excon::Errors::BadRequest, "Bad flavor")
91
+ end
92
+
93
+ it "re-raises it as a Kitchen failure" do
94
+ expect { driver.create(state) }
95
+ .to raise_error(Kitchen::ActionFailed, /Bad flavor/)
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "#create with rackconnect_wait" do
101
+ let(:config) { credentials.merge(rackconnect_wait: true) }
102
+
103
+ it "waits for rackconnect" do
104
+ expect(driver).to receive(:rackconnect_check).with(server)
105
+
106
+ driver.create(state)
107
+ end
108
+
109
+ it "waits on the rackconnect automation status" do
110
+ driver.send(:rackconnect_check, server)
111
+
112
+ expect(server).to have_received(:wait_for)
113
+ end
114
+
115
+ # RackConnect hands the server a new public address as part of its work.
116
+ # Without the refresh, the stale address goes into the state file and the
117
+ # transport connects to nothing.
118
+ it "refreshes the server so the new public IP is picked up" do
119
+ driver.send(:rackconnect_check, server)
120
+
121
+ expect(server).to have_received(:update)
122
+ end
123
+ end
124
+
125
+ describe "#create with servicelevel_wait" do
126
+ let(:config) { credentials.merge(servicelevel_wait: true) }
127
+
128
+ it "waits for managed service level automation" do
129
+ expect(driver).to receive(:servicelevel_check).with(server)
130
+
131
+ driver.create(state)
132
+ end
133
+
134
+ it "waits on the service level automation status" do
135
+ driver.send(:servicelevel_check, server)
136
+
137
+ expect(server).to have_received(:wait_for)
138
+ end
139
+ end
140
+
141
+ describe "#create with servicenet" do
142
+ let(:config) { credentials.merge(servicenet: true) }
143
+
144
+ it "records the private IP as the hostname" do
145
+ driver.create(state)
146
+
147
+ expect(state[:hostname]).to eq("10.9.8.7")
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,63 @@
1
+ RSpec.describe Kitchen::Driver::Rackspace do
2
+ include_context "a driver for an instance"
3
+
4
+ describe "#destroy" do
5
+ let(:server) { double("server", nil?: false, destroy: true) }
6
+ let(:servers) { instance_double(Fog::Compute::RackspaceV2::Servers, get: server) }
7
+ let(:state) { { server_id: "12345", hostname: "example.com" } }
8
+
9
+ before do
10
+ allow_any_instance_of(described_class).to receive(:compute)
11
+ .and_return(double("compute", servers:))
12
+ end
13
+
14
+ context "a live server that needs to be destroyed" do
15
+ it "destroys the server" do
16
+ expect(server).to receive(:destroy)
17
+
18
+ driver.destroy(state)
19
+ end
20
+
21
+ it "clears the server out of the state" do
22
+ driver.destroy(state)
23
+
24
+ expect(state).to_not include(:server_id)
25
+ expect(state).to_not include(:hostname)
26
+ end
27
+
28
+ it "says which server it destroyed" do
29
+ driver.destroy(state)
30
+
31
+ expect(logged_output.string).to match(/instance <12345> destroyed/)
32
+ end
33
+ end
34
+
35
+ context "no server ID present" do
36
+ let(:state) { {} }
37
+
38
+ it "does nothing" do
39
+ expect(driver).to_not receive(:compute)
40
+
41
+ expect { driver.destroy(state) }.to_not raise_error
42
+ end
43
+ end
44
+
45
+ context "a server that was already destroyed" do
46
+ let(:servers) do
47
+ instance_double(Fog::Compute::RackspaceV2::Servers, get: nil)
48
+ end
49
+
50
+ it "does not try to destroy the server again" do
51
+ expect(server).to_not receive(:destroy)
52
+
53
+ driver.destroy(state)
54
+ end
55
+
56
+ it "still clears the server out of the state" do
57
+ driver.destroy(state)
58
+
59
+ expect(state).to_not include(:server_id)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,58 @@
1
+ RSpec.describe Kitchen::Driver::Rackspace do
2
+ include_context "a driver for an instance"
3
+
4
+ describe "#default_image" do
5
+ {
6
+ "ubuntu-12.04" => "f2d30a56-bc2b-4906-8027-92f8a45bbb10",
7
+ "ubuntu-12" => "f2d30a56-bc2b-4906-8027-92f8a45bbb10",
8
+ "ubuntu-14.04" => "e6baca58-c5f4-48d3-901a-abdeb0cfe907",
9
+ "ubuntu-14" => "e6baca58-c5f4-48d3-901a-abdeb0cfe907",
10
+ "ubuntu" => "9b3ae961-0ba0-4d5a-973f-2e79043f0ddd",
11
+ "centos-6" => "7d791876-4c8f-44a2-8d4b-e84bfb0b1c8c",
12
+ "centos" => "1a79f262-33d2-428c-924b-9852a6c15ea8",
13
+ }.each do |platform, id|
14
+ context "the platform is #{platform}" do
15
+ let(:platform_name) { platform }
16
+
17
+ it "resolves it to the right image ID" do
18
+ expect(driver[:image_id]).to eq(id)
19
+ end
20
+ end
21
+ end
22
+
23
+ # The bundled table has not been regenerated since 2016, so every modern
24
+ # platform name lands here. `image_id` then has no value, `required_config`
25
+ # rejects it, and the user has to supply one -- which is exactly what the
26
+ # README tells them to do. If this ever starts returning something, the
27
+ # README is wrong.
28
+ context "a platform the bundled table does not know" do
29
+ let(:platform_name) { "ubuntu-24.04" }
30
+
31
+ it "has no image to offer" do
32
+ expect(driver.default_image).to be_nil
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "the bundled image table" do
38
+ subject(:images) { driver.send(:images) }
39
+
40
+ it "parses as JSON" do
41
+ expect { images }.to_not raise_error
42
+ end
43
+
44
+ it "is not empty" do
45
+ expect(images).to_not be_empty
46
+ end
47
+
48
+ it "maps every platform name to a UUID" do
49
+ expect(images.values).to all(match(/\A\h{8}(-\h{4}){3}-\h{12}\z/))
50
+ end
51
+
52
+ it "is only read from disk once" do
53
+ expect(IO).to receive(:read).once.and_call_original
54
+
55
+ 2.times { driver.send(:images) }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,14 @@
1
+ RSpec.describe Kitchen::Driver::Rackspace do
2
+ include_context "a driver for an instance"
3
+
4
+ describe "plugin metadata" do
5
+ it "declares the driver API version" do
6
+ expect(described_class.instance_variable_get(:@api_version)).to eq(2)
7
+ end
8
+
9
+ it "reports its own gem version to kitchen diagnose" do
10
+ expect(driver.diagnose_plugin[:version])
11
+ .to eq(Kitchen::Driver::RACKSPACE_VERSION)
12
+ end
13
+ end
14
+ end