chef-provisioning-google 0.1.0
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/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
|