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.
- checksums.yaml +4 -4
- data/.github/workflows/publish.yaml +1 -1
- data/.release-please-manifest.json +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +1 -3
- data/.yamllint +6 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +111 -3
- data/CONTRIBUTING.md +207 -0
- data/Gemfile +7 -5
- data/README.md +327 -58
- data/Rakefile +18 -2
- data/helpers/dump_flavor_list.rb +46 -0
- data/helpers/dump_image_list.rb +97 -66
- data/kitchen-rackspace.gemspec +6 -4
- data/lib/kitchen/driver/rackspace.rb +201 -12
- data/lib/kitchen/driver/rackspace_version.rb +4 -1
- data/spec/kitchen/driver/rackspace/compute_spec.rb +162 -0
- data/spec/kitchen/driver/rackspace/configuration_spec.rb +210 -0
- data/spec/kitchen/driver/rackspace/create_spec.rb +150 -0
- data/spec/kitchen/driver/rackspace/destroy_spec.rb +63 -0
- data/spec/kitchen/driver/rackspace/image_spec.rb +58 -0
- data/spec/kitchen/driver/rackspace/plugin_spec.rb +14 -0
- data/spec/kitchen/driver/rackspace/server_name_spec.rb +63 -0
- data/spec/kitchen/driver/rackspace/status_spec.rb +151 -0
- data/spec/kitchen/driver/rackspace/tcp_check_spec.rb +37 -0
- data/spec/spec_helper.rb +21 -5
- data/spec/support/driver_context.rb +51 -0
- data/spec/support/rackspace_environment.rb +27 -0
- metadata +24 -12
- data/.github/dependabot.yml +0 -8
- data/spec/kitchen/driver/rackspace_spec.rb +0 -664
|
@@ -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
|