chef-provisioning-google 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +121 -0
- data/Rakefile +22 -0
- data/lib/chef/provider/google_key_pair.rb +172 -0
- data/lib/chef/provisioning/driver_init/google.rb +3 -0
- data/lib/chef/provisioning/google_driver.rb +5 -0
- data/lib/chef/provisioning/google_driver/client/global_operations.rb +18 -0
- data/lib/chef/provisioning/google_driver/client/google_base.rb +72 -0
- data/lib/chef/provisioning/google_driver/client/google_compute_error.rb +16 -0
- data/lib/chef/provisioning/google_driver/client/instance.rb +64 -0
- data/lib/chef/provisioning/google_driver/client/instances.rb +98 -0
- data/lib/chef/provisioning/google_driver/client/metadata.rb +112 -0
- data/lib/chef/provisioning/google_driver/client/operation.rb +23 -0
- data/lib/chef/provisioning/google_driver/client/operations_base.rb +44 -0
- data/lib/chef/provisioning/google_driver/client/projects.rb +39 -0
- data/lib/chef/provisioning/google_driver/client/zone_operations.rb +18 -0
- data/lib/chef/provisioning/google_driver/credentials.rb +63 -0
- data/lib/chef/provisioning/google_driver/driver.rb +313 -0
- data/lib/chef/provisioning/google_driver/resources.rb +0 -0
- data/lib/chef/provisioning/google_driver/version.rb +7 -0
- data/lib/chef/resource/google_key_pair.rb +50 -0
- data/spec/chef/provisioning/google_driver/client/instances_spec.rb +205 -0
- data/spec/chef/provisioning/google_driver/client/operations_spec.rb +62 -0
- data/spec/chef/provisioning/google_driver/client/projects_spec.rb +162 -0
- data/spec/chef/provisioning/google_driver/client/services_helper.rb +33 -0
- data/spec/spec_helper.rb +0 -0
- metadata +222 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
require "chef/provisioning/google_driver/client/instances"
|
2
|
+
require "chef/provisioning/google_driver/client/google_compute_error"
|
3
|
+
require_relative "services_helper"
|
4
|
+
|
5
|
+
include Chef::Provisioning::GoogleDriver::Client
|
6
|
+
|
7
|
+
RSpec.describe Instances do
|
8
|
+
include ServicesHelper
|
9
|
+
|
10
|
+
before(:example) do
|
11
|
+
setup_service("instances", %w{get insert delete start stop})
|
12
|
+
@instances_client = Instances.new(@google, ServicesHelper::PROJECT,
|
13
|
+
ServicesHelper::ZONE)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when start is called" do
|
17
|
+
|
18
|
+
def expect_start(status, body)
|
19
|
+
expect_call("instances.start", { instance: "instance_name" },
|
20
|
+
nil, status, body)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "raises an error if the API call failed" do
|
24
|
+
expect_start(400, {})
|
25
|
+
expect do
|
26
|
+
@instances_client.start("instance_name")
|
27
|
+
end.to raise_error(GoogleComputeError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises an error if the response contains errors" do
|
31
|
+
expect_start(200, { error: { errors: [{ message: "failure!" }] } })
|
32
|
+
expect do
|
33
|
+
@instances_client.start("instance_name")
|
34
|
+
end.to raise_error(GoogleComputeError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns a valid operation if everything goes well" do
|
38
|
+
expect_start(200, { name: "name", status: "LOL" })
|
39
|
+
operation = @instances_client.start("instance_name")
|
40
|
+
expect(operation.name).to eq("name")
|
41
|
+
expect(operation).not_to be_done
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when stop is called" do
|
47
|
+
|
48
|
+
def expect_stop(status, body)
|
49
|
+
expect_call("instances.stop", { instance: "instance_name" },
|
50
|
+
nil, status, body)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "raises an error if the API call failed" do
|
54
|
+
expect_stop(400, {})
|
55
|
+
expect do
|
56
|
+
@instances_client.stop("instance_name")
|
57
|
+
end.to raise_error(GoogleComputeError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises an error if the response contains errors" do
|
61
|
+
expect_stop(200, { error: { errors: [{ message: "failure!" }] } })
|
62
|
+
expect do
|
63
|
+
@instances_client.stop("instance_name")
|
64
|
+
end.to raise_error(GoogleComputeError)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns a valid operation if everything goes well" do
|
68
|
+
expect_stop(200, { name: "name", status: "LOL" })
|
69
|
+
operation = @instances_client.stop("instance_name")
|
70
|
+
expect(operation.name).to eq("name")
|
71
|
+
expect(operation).not_to be_done
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when delete is called" do
|
77
|
+
|
78
|
+
def expect_delete(status, body)
|
79
|
+
expect_call("instances.delete", { instance: "instance_name" },
|
80
|
+
nil, status, body)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "raises an error if the API call failed" do
|
84
|
+
expect_delete(400, {})
|
85
|
+
expect do
|
86
|
+
@instances_client.delete("instance_name")
|
87
|
+
end.to raise_error(GoogleComputeError)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "raises an error if the response contains errors" do
|
91
|
+
expect_delete(200, { error: { errors: [{ message: "failure!" }] } })
|
92
|
+
expect do
|
93
|
+
@instances_client.delete("instance_name")
|
94
|
+
end.to raise_error(GoogleComputeError)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "returns a valid operation if everything goes well" do
|
98
|
+
expect_delete(200, { name: "name", status: "LOL" })
|
99
|
+
operation = @instances_client.delete("instance_name")
|
100
|
+
expect(operation.name).to eq("name")
|
101
|
+
expect(operation).not_to be_done
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when create is called" do
|
107
|
+
|
108
|
+
def expect_create(status, body)
|
109
|
+
expect_call("instances.insert", {}, { some_option: 123 },
|
110
|
+
status, body)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "raises an error if the API call failed" do
|
114
|
+
expect_create(400, {})
|
115
|
+
expect do
|
116
|
+
@instances_client.create(some_option: 123)
|
117
|
+
end.to raise_error(GoogleComputeError)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "raises an error if the response contains errors" do
|
121
|
+
expect_create(200, { error: { errors: [{ message: "failure!" }] } })
|
122
|
+
expect do
|
123
|
+
@instances_client.create(some_option: 123)
|
124
|
+
end.to raise_error(GoogleComputeError)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "returns a valid operation if everything goes well" do
|
128
|
+
expect_create(200, { name: "name", status: "DONE" })
|
129
|
+
operation = @instances_client.create(some_option: 123)
|
130
|
+
expect(operation.name).to eq("name")
|
131
|
+
expect(operation).to be_done
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
context "when get is called" do
|
137
|
+
|
138
|
+
def expect_get(status, body)
|
139
|
+
expect_call("instances.get", { instance: "instance_name" },
|
140
|
+
nil, status, body)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'returns nil if the instance couldn\'t be found' do
|
144
|
+
expect_get(404, {})
|
145
|
+
@instances_client.get("instance_name")
|
146
|
+
end
|
147
|
+
|
148
|
+
it "raises an error if the API call failed" do
|
149
|
+
expect_get(400, {})
|
150
|
+
expect do
|
151
|
+
@instances_client.get("instance_name")
|
152
|
+
end.to raise_error(GoogleComputeError)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "raises an error if the response contains errors" do
|
156
|
+
expect_get(200, { error: { errors: [{ message: "failure!" }] } })
|
157
|
+
expect do
|
158
|
+
@instances_client.get("instance_name")
|
159
|
+
end.to raise_error(GoogleComputeError)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "returns a valid instance if everything goes well" do
|
163
|
+
networkInterface = { accessConfigs: [{ natIP: "ip" }] }
|
164
|
+
expect_get(200, {
|
165
|
+
name: "instance_name",
|
166
|
+
id: "instance_id",
|
167
|
+
status: "RUNNING",
|
168
|
+
networkInterfaces: [networkInterface],
|
169
|
+
})
|
170
|
+
instance = @instances_client.get("instance_name")
|
171
|
+
expect(instance.name).to eq("instance_name")
|
172
|
+
expect(instance.id).to eq("instance_id")
|
173
|
+
expect(instance.status).to eq("RUNNING")
|
174
|
+
expect(instance).to be_running
|
175
|
+
expect(instance).not_to be_stopped
|
176
|
+
expect(instance).not_to be_stopping
|
177
|
+
expect(instance).not_to be_terminated
|
178
|
+
expect(instance.determine_remote_host).to eq("ip")
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
context "when wait_for_status is called" do
|
184
|
+
|
185
|
+
def expect_get(name, body)
|
186
|
+
expect_call("instances.get", { instance: name },
|
187
|
+
nil, 200, body)
|
188
|
+
end
|
189
|
+
|
190
|
+
before(:example) do
|
191
|
+
@action_handler = double("action_handler")
|
192
|
+
expect(@action_handler).to receive(:report_progress)
|
193
|
+
end
|
194
|
+
|
195
|
+
it "returns a valid instance if everything goes well" do
|
196
|
+
expect_get("instance_name", { name: "instance_name2" })
|
197
|
+
instance = @instances_client.get("instance_name")
|
198
|
+
expect_get("instance_name2", { name: "instance_name2", status: "RUNNING" })
|
199
|
+
running_instance = @instances_client.wait_for_status(@action_handler,
|
200
|
+
instance, "RUNNING")
|
201
|
+
expect(running_instance.name).to eq("instance_name2")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "chef/provisioning/google_driver/client/global_operations"
|
2
|
+
require "chef/provisioning/google_driver/client/zone_operations"
|
3
|
+
require "chef/provisioning/google_driver/client/google_compute_error"
|
4
|
+
require "chef/provisioning/google_driver/client/operation"
|
5
|
+
require_relative "services_helper"
|
6
|
+
|
7
|
+
include Chef::Provisioning::GoogleDriver::Client
|
8
|
+
|
9
|
+
RSpec.shared_examples "Operations" do |service_name, client_class|
|
10
|
+
|
11
|
+
include ServicesHelper
|
12
|
+
|
13
|
+
before(:example) do
|
14
|
+
setup_service(service_name, ["get"])
|
15
|
+
@service_name = service_name
|
16
|
+
@operations_client = client_class.new(@google, ServicesHelper::PROJECT,
|
17
|
+
ServicesHelper::ZONE)
|
18
|
+
@operation = Operation.new({ body: { name: "op", status: "NOT_DONE" } })
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when get is called" do
|
22
|
+
|
23
|
+
def expect_get(status, body)
|
24
|
+
expect_call("#{@service_name}.get", { operation: "op" }, nil, status, body)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "raises an error if the API call failed" do
|
28
|
+
expect_get(400, {})
|
29
|
+
expect do
|
30
|
+
@operations_client.get(@operation)
|
31
|
+
end.to raise_error(GoogleComputeError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "raises an error if the response contains errors" do
|
35
|
+
expect_get(200, { error: { errors: [{ message: "failure!" }] } })
|
36
|
+
expect do
|
37
|
+
@operations_client.get(@operation)
|
38
|
+
end.to raise_error(GoogleComputeError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns a valid result if everything goes well" do
|
42
|
+
expect_get(200, { name: "op", status: "NOT_DONE" })
|
43
|
+
operation = @operations_client.get(@operation)
|
44
|
+
expect(operation.name).to eq("op")
|
45
|
+
expect(operation).not_to be_done
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
RSpec.describe GlobalOperations do
|
53
|
+
|
54
|
+
it_behaves_like "Operations", "global_operations", GlobalOperations
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
RSpec.describe ZoneOperations do
|
59
|
+
|
60
|
+
it_behaves_like "Operations", "zone_operations", ZoneOperations
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require "chef/provisioning/google_driver/client/projects"
|
2
|
+
require "chef/provisioning/google_driver/client/google_compute_error"
|
3
|
+
require_relative "services_helper"
|
4
|
+
|
5
|
+
include Chef::Provisioning::GoogleDriver::Client
|
6
|
+
|
7
|
+
RSpec.describe Projects do
|
8
|
+
|
9
|
+
FINGERPRINT = "asdf"
|
10
|
+
ITEMS = [{ key: "sshKeys", value: "testuser1:key1\ntestuser2:key2" },
|
11
|
+
{ key: "chef-provisioning-google_ssh-mappings",
|
12
|
+
value: "testuser3:key3\ntestuser4:key4" }]
|
13
|
+
SSH_KEYS = [%w{testuser1 key1}, %w{testuser2 key2}]
|
14
|
+
SSH_MAPPINGS = { "testuser3" => "key3", "testuser4" => "key4" }
|
15
|
+
|
16
|
+
include ServicesHelper
|
17
|
+
|
18
|
+
before(:example) do
|
19
|
+
setup_service("projects", %w{get set_common_instance_metadata})
|
20
|
+
@projects_client = Projects.new(@google, ServicesHelper::PROJECT,
|
21
|
+
ServicesHelper::ZONE)
|
22
|
+
end
|
23
|
+
|
24
|
+
def expect_get(status, body)
|
25
|
+
expect_call("projects.get", {}, nil, status, body)
|
26
|
+
end
|
27
|
+
|
28
|
+
def expect_successful_get
|
29
|
+
expect_get(200, { name: "name", commonInstanceMetadata: {
|
30
|
+
fingerprint: FINGERPRINT,
|
31
|
+
items: ITEMS,
|
32
|
+
} })
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when get is called" do
|
36
|
+
|
37
|
+
it "raises an error if the API call failed" do
|
38
|
+
expect_get(400, {})
|
39
|
+
expect do
|
40
|
+
@projects_client.get
|
41
|
+
end.to raise_error(GoogleComputeError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "raises an error if the response contains errors" do
|
45
|
+
expect_get(200, { error: { errors: [{ message: "failure!" }] } })
|
46
|
+
expect do
|
47
|
+
@projects_client.get
|
48
|
+
end.to raise_error(GoogleComputeError)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns a valid result if everything goes well" do
|
52
|
+
expect_successful_get
|
53
|
+
metadata = @projects_client.get
|
54
|
+
expect(metadata).not_to be_changed
|
55
|
+
expect(metadata.ssh_keys).to eq(SSH_KEYS)
|
56
|
+
expect(metadata.ssh_mappings).to eq(SSH_MAPPINGS)
|
57
|
+
expect(metadata).not_to be_changed
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when set_common_instance_metadata is called" do
|
63
|
+
before(:example) do
|
64
|
+
expect_successful_get
|
65
|
+
@metadata = @projects_client.get
|
66
|
+
end
|
67
|
+
|
68
|
+
def expect_set_common_instance_metadata(ssh_keys, ssh_mappings, status,
|
69
|
+
response_body)
|
70
|
+
request_body = {
|
71
|
+
items: [{ key: "chef-provisioning-google_ssh-mappings",
|
72
|
+
value: ssh_mappings },
|
73
|
+
{ key: "sshKeys", value: ssh_keys }],
|
74
|
+
fingerprint: FINGERPRINT,
|
75
|
+
}
|
76
|
+
expect_call("projects.set_common_instance_metadata", {}, request_body,
|
77
|
+
status, response_body)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'does nothing if the metadata hasn\'t been changed' do
|
81
|
+
@metadata.ssh_keys
|
82
|
+
@metadata.ssh_mappings
|
83
|
+
@projects_client.set_common_instance_metadata(@metadata)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "raises an error if the API call failed" do
|
87
|
+
expect_set_common_instance_metadata(
|
88
|
+
"testuser1:key1\ntestuser2:key2\nfoo:bar",
|
89
|
+
"testuser3:key3\ntestuser4:key4",
|
90
|
+
400,
|
91
|
+
{})
|
92
|
+
# Change something such that the API call actually happens.
|
93
|
+
@metadata.ensure_key("foo", "bar")
|
94
|
+
expect do
|
95
|
+
@projects_client.set_common_instance_metadata(@metadata)
|
96
|
+
end.to raise_error(GoogleComputeError)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "raises an error if the response contains errors" do
|
100
|
+
expect_set_common_instance_metadata(
|
101
|
+
"testuser1:key1\ntestuser2:key2\nfoo:bar",
|
102
|
+
"testuser3:key3\ntestuser4:key4",
|
103
|
+
200,
|
104
|
+
{ error: { errors: [{ message: "failure!" }] } })
|
105
|
+
# Change something such that the API call actually happens.
|
106
|
+
@metadata.ensure_key("foo", "bar")
|
107
|
+
expect do
|
108
|
+
@projects_client.set_common_instance_metadata(@metadata)
|
109
|
+
end.to raise_error(GoogleComputeError)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "returns a valid result if an ssh key is added successfully" do
|
113
|
+
expect_set_common_instance_metadata(
|
114
|
+
"testuser1:key1\ntestuser2:key2\nfoo:bar",
|
115
|
+
"testuser3:key3\ntestuser4:key4",
|
116
|
+
200,
|
117
|
+
{ status: "DONE" })
|
118
|
+
@metadata.ensure_key("foo", "bar")
|
119
|
+
expect(@metadata).to be_changed
|
120
|
+
operation = @projects_client.set_common_instance_metadata(@metadata)
|
121
|
+
expect(operation).to be_done
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns a valid result if an ssh mapping is added successfully" do
|
125
|
+
expect_set_common_instance_metadata(
|
126
|
+
"testuser1:key1\ntestuser2:key2",
|
127
|
+
"testuser3:key3\ntestuser4:key4\nfoo:bar",
|
128
|
+
200,
|
129
|
+
{ status: "DONE" })
|
130
|
+
@metadata.set_ssh_mapping("foo", "bar")
|
131
|
+
expect(@metadata).to be_changed
|
132
|
+
operation = @projects_client.set_common_instance_metadata(@metadata)
|
133
|
+
expect(operation).to be_done
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns a valid result if an ssh key is removed successfully" do
|
137
|
+
expect_set_common_instance_metadata(
|
138
|
+
"testuser1:key1",
|
139
|
+
"testuser3:key3\ntestuser4:key4",
|
140
|
+
200,
|
141
|
+
{ status: "DONE" })
|
142
|
+
@metadata.delete_ssh_key("key2")
|
143
|
+
expect(@metadata).to be_changed
|
144
|
+
operation = @projects_client.set_common_instance_metadata(@metadata)
|
145
|
+
expect(operation).to be_done
|
146
|
+
end
|
147
|
+
|
148
|
+
it "returns a valid result if an ssh mapping is removed successfully" do
|
149
|
+
expect_set_common_instance_metadata(
|
150
|
+
"testuser1:key1\ntestuser2:key2",
|
151
|
+
"testuser3:key3",
|
152
|
+
200,
|
153
|
+
{ status: "DONE" })
|
154
|
+
@metadata.delete_ssh_mapping("testuser4")
|
155
|
+
expect(@metadata).to be_changed
|
156
|
+
operation = @projects_client.set_common_instance_metadata(@metadata)
|
157
|
+
expect(operation).to be_done
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "ffi_yajl"
|
2
|
+
|
3
|
+
# Helper module for testing classes that do calls to the Google API.
|
4
|
+
module ServicesHelper
|
5
|
+
PROJECT = "test-project"
|
6
|
+
ZONE = "test-zone"
|
7
|
+
DEFAULT_PARAMETERS = { project: PROJECT, zone: ZONE }
|
8
|
+
|
9
|
+
def setup_service(service_name, methods)
|
10
|
+
service = double(service_name)
|
11
|
+
methods.each do |m|
|
12
|
+
allow(service).to receive(m.to_sym).and_return("#{service_name}.#{m}")
|
13
|
+
end
|
14
|
+
compute = double("compute")
|
15
|
+
allow(compute).to receive(service_name.to_sym).and_return(service)
|
16
|
+
@google = double("google")
|
17
|
+
allow(@google).to receive(:discovered_api).and_return(compute)
|
18
|
+
end
|
19
|
+
|
20
|
+
def expect_call(method, parameters, request_body,
|
21
|
+
status, response_body)
|
22
|
+
inner_response = double("inner_response")
|
23
|
+
allow(inner_response).to receive(:status).and_return(status)
|
24
|
+
encoded_body = FFI_Yajl::Encoder.encode(response_body)
|
25
|
+
allow(inner_response).to receive(:body).and_return(encoded_body)
|
26
|
+
response = double("response")
|
27
|
+
allow(response).to receive(:response).and_return(inner_response)
|
28
|
+
allow(@google).to receive(:execute).with(
|
29
|
+
hash_including(api_method: method,
|
30
|
+
parameters: parameters.merge(DEFAULT_PARAMETERS),
|
31
|
+
body_object: request_body)).and_return(response)
|
32
|
+
end
|
33
|
+
end
|