knife-google 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/README.md +34 -7
- data/lib/chef/knife/google_base.rb +1 -0
- data/lib/chef/knife/google_server_create.rb +49 -8
- data/lib/chef/knife/google_server_delete.rb +9 -7
- data/lib/chef/knife/google_server_list.rb +15 -15
- data/lib/google/compute/client.rb +26 -18
- data/lib/google/compute/creatable_resource_collection.rb +6 -0
- data/lib/google/compute/exception.rb +2 -0
- data/lib/knife-google/version.rb +1 -1
- data/spec/chef/knife/google_base_spec.rb +1 -1
- data/spec/chef/knife/google_server_create_spec.rb +36 -16
- data/spec/chef/knife/google_server_delete_spec.rb +22 -0
- data/spec/chef/knife/google_server_list_spec.rb +13 -14
- data/spec/data/{compute-v1beta14.json → compute-v1beta15.json} +4122 -1228
- data/spec/data/disk.json +2 -2
- data/spec/data/firewall.json +2 -2
- data/spec/data/global_operation.json +3 -3
- data/spec/data/image.json +2 -2
- data/spec/data/kernel.json +1 -1
- data/spec/data/machine_type.json +3 -3
- data/spec/data/network.json +1 -1
- data/spec/data/project.json +1 -1
- data/spec/data/serial_port_output.json +1 -1
- data/spec/data/server.json +6 -6
- data/spec/data/snapshot.json +2 -2
- data/spec/data/zone.json +1 -1
- data/spec/data/zone_operation.json +3 -3
- data/spec/google/compute/disk_spec.rb +17 -7
- data/spec/google/compute/firewall_spec.rb +27 -26
- data/spec/google/compute/global_operation_spec.rb +6 -6
- data/spec/google/compute/image_spec.rb +11 -11
- data/spec/google/compute/kernel_spec.rb +4 -4
- data/spec/google/compute/machine_type_spec.rb +4 -4
- data/spec/google/compute/network_spec.rb +9 -9
- data/spec/google/compute/project_spec.rb +14 -14
- data/spec/google/compute/server_spec.rb +23 -23
- data/spec/google/compute/snapshot_spec.rb +6 -18
- data/spec/google/compute/zone_operation_spec.rb +6 -6
- data/spec/google/compute/zone_spec.rb +5 -4
- data/spec/support/mocks.rb +2 -2
- metadata +112 -117
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -65,6 +65,19 @@ or, for Gemfile:
|
|
65
65
|
gem 'knife-google'
|
66
66
|
```
|
67
67
|
|
68
|
+
There is a long standing issue in Ruby where the net/http library by default does not check the validity of an SSL certificate during a TLS handshake.
|
69
|
+
|
70
|
+
To configure Windows system to validate SSL certificate please download
|
71
|
+
[cacert.pem](http://curl.haxx.se/ca/cacert.pem) file and save to C: drive. Now make ruby aware of your certificate authority by setting SSL_CERT_FILE.
|
72
|
+
|
73
|
+
To set this in your current command prompt session, type:
|
74
|
+
|
75
|
+
```sh
|
76
|
+
set SSL_CERT_FILE = C:\cacert.pem
|
77
|
+
```
|
78
|
+
|
79
|
+
On Linux system the configuration for SSL certificate validation is present by default.
|
80
|
+
|
68
81
|
Depending on your system's configuration, you may need to run this command
|
69
82
|
with root/Administrator privileges.
|
70
83
|
|
@@ -126,7 +139,7 @@ Some usage examples follow:
|
|
126
139
|
$ knife google server list -Z us-central2-a
|
127
140
|
|
128
141
|
# Create a server
|
129
|
-
$ knife google server create www1 -m n1-standard-1 -I
|
142
|
+
$ knife google server create www1 -m n1-standard-1 -I debian-7-wheezy-v20130723 -Z us-central2-a -i ~/.ssh/id_rsa -x jdoe
|
130
143
|
|
131
144
|
# Delete a server (along with Chef node and API client via --purge)
|
132
145
|
$ knife google server delete www1 --purge -Z us-central2-a
|
@@ -203,12 +216,26 @@ and upcoming maintenance windows. The output should look similar to:
|
|
203
216
|
### knife google server create
|
204
217
|
|
205
218
|
Use this command to create a new Google Compute Engine server (a.k.a.
|
206
|
-
instance). You must specify a name, the machine type, the zone, and
|
207
|
-
image.
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
219
|
+
instance). You must specify a name, the machine type, the zone, and
|
220
|
+
the the image name. Images provided by Google follow this naming
|
221
|
+
convention:
|
222
|
+
|
223
|
+
```
|
224
|
+
debian-7-wheezy-vYYYYMMDD
|
225
|
+
debian-6-squeeze-vYYYYMMDD
|
226
|
+
centos-6-vYYYYMMDD
|
227
|
+
```
|
228
|
+
|
229
|
+
By default, the plugin will look for the specified image in the instance's
|
230
|
+
primary project first and then consult GCE's officially supported image
|
231
|
+
locations. The `-J IMAGE_PROJECT_ID` option can be specified to force the
|
232
|
+
plugin to look for the image in an alternate project location.
|
233
|
+
|
234
|
+
Note that if you are bootstrapping the node, make sure to follow the
|
235
|
+
preparation instructions earlier and use the `-x` and `-i` commands
|
236
|
+
to specify the username and the identity file for that user. Make sure
|
237
|
+
to use the private key file (e.g. `~/.ssh/id_rsa`) for the identity
|
238
|
+
file and *not* the public key file.
|
212
239
|
|
213
240
|
See the extended options that also allow bootstrapping the node with
|
214
241
|
`knife google server create --help`.
|
@@ -44,11 +44,16 @@ class Chef
|
|
44
44
|
:description => "The Image for the server",
|
45
45
|
:required => true
|
46
46
|
|
47
|
+
option :image_project_id,
|
48
|
+
:short => "-J IMAGE_PROJECT_ID",
|
49
|
+
:long => "--google-compute-image-project-id IMAGE_PROJECT_ID",
|
50
|
+
:description => "The project-id containing the Image (debian-cloud, centos-cloud, etc)",
|
51
|
+
:default => ""
|
52
|
+
|
47
53
|
option :zone,
|
48
54
|
:short => "-Z ZONE",
|
49
55
|
:long => "--google-compute-zone ZONE",
|
50
|
-
:description => "The Zone for this server"
|
51
|
-
:required => true
|
56
|
+
:description => "The Zone for this server"
|
52
57
|
|
53
58
|
option :network,
|
54
59
|
:short => "-n NETWORK",
|
@@ -265,23 +270,59 @@ class Chef
|
|
265
270
|
end
|
266
271
|
|
267
272
|
begin
|
268
|
-
zone = client.zones.get(config[:zone]).self_link
|
273
|
+
zone = client.zones.get(config[:zone] || Chef::Config[:knife][:google_compute_zone]).self_link
|
269
274
|
rescue Google::Compute::ResourceNotFound
|
270
|
-
ui.error("Zone '#{config[:zone]}' not found")
|
275
|
+
ui.error("Zone '#{config[:zone] || Chef::Config[:knife][:google_compute_zone]}' not found")
|
276
|
+
exit 1
|
277
|
+
rescue Google::Compute::ParameterValidation
|
278
|
+
ui.error("Must specify zone in knife config file or in command line as an option. Try --help.")
|
271
279
|
exit 1
|
272
280
|
end
|
281
|
+
|
273
282
|
begin
|
274
|
-
machine_type = client.machine_types.get(config[:machine_type]).self_link
|
283
|
+
machine_type = client.machine_types.get(:name=>config[:machine_type], :zone=>selflink2name(zone)).self_link
|
275
284
|
rescue Google::Compute::ResourceNotFound
|
276
285
|
ui.error("MachineType '#{config[:machine_type]}' not found")
|
277
286
|
exit 1
|
278
287
|
end
|
288
|
+
|
289
|
+
(checked_custom, checked_all) = false
|
279
290
|
begin
|
280
|
-
|
291
|
+
image_project = config[:image_project_id]
|
292
|
+
machine_type=~Regexp.new('/projects/(.*?)/')
|
293
|
+
project = $1
|
294
|
+
if image_project.empty?
|
295
|
+
unless checked_custom
|
296
|
+
checked_custom = true
|
297
|
+
ui.info("Looking for Image '#{config[:image]}' in Project '#{project}'")
|
298
|
+
image = client.images.get(:project=>project, :name=>config[:image]).self_link
|
299
|
+
else
|
300
|
+
case config[:image].downcase
|
301
|
+
when /debian/
|
302
|
+
project = 'debian-cloud'
|
303
|
+
ui.info("Looking for Image '#{config[:image]}' in Project '#{project}'")
|
304
|
+
when /centos/
|
305
|
+
project = 'centos-cloud'
|
306
|
+
ui.info("Looking for Image '#{config[:image]}' in Project '#{project}'")
|
307
|
+
end
|
308
|
+
checked_all = true
|
309
|
+
image = client.images.get(:project=>project, :name=>config[:image]).self_link
|
310
|
+
end
|
311
|
+
else
|
312
|
+
checked_all = true
|
313
|
+
project = image_project
|
314
|
+
image = client.images.get(:project=>project, :name=>config[:image]).self_link
|
315
|
+
end
|
316
|
+
ui.info("Found Image '#{config[:image]}' in Project '#{project}'")
|
281
317
|
rescue Google::Compute::ResourceNotFound
|
282
|
-
|
283
|
-
|
318
|
+
unless checked_all then
|
319
|
+
retry
|
320
|
+
else
|
321
|
+
ui.error("Image '#{config[:image]}' not found")
|
322
|
+
exit 1
|
323
|
+
end
|
284
324
|
end
|
325
|
+
|
285
326
|
begin
|
286
327
|
network = client.networks.get(config[:network]).self_link
|
287
328
|
rescue Google::Compute::ResourceNotFound
|
@@ -21,6 +21,7 @@ class Chef
|
|
21
21
|
include Knife::GoogleBase
|
22
22
|
|
23
23
|
deps do
|
24
|
+
require 'chef/api_client'
|
24
25
|
require 'google/compute'
|
25
26
|
end
|
26
27
|
|
@@ -31,8 +32,7 @@ class Chef
|
|
31
32
|
option :zone,
|
32
33
|
:short => "-Z ZONE",
|
33
34
|
:long => "--google-compute-zone ZONE",
|
34
|
-
:description => "The Zone for this server"
|
35
|
-
:required => true
|
35
|
+
:description => "The Zone for this server"
|
36
36
|
|
37
37
|
option :purge,
|
38
38
|
:short => "-P",
|
@@ -60,9 +60,12 @@ class Chef
|
|
60
60
|
|
61
61
|
def run
|
62
62
|
begin
|
63
|
-
zone = client.zones.get(config[:zone]).self_link
|
63
|
+
zone = client.zones.get(config[:zone] || Chef::Config[:knife][:google_compute_zone]).self_link
|
64
64
|
rescue Google::Compute::ResourceNotFound
|
65
|
-
ui.error("Zone '#{config[:zone]}' not found")
|
65
|
+
ui.error("Zone '#{config[:zone] || Chef::Config[:knife][:google_compute_zone]}' not found")
|
66
|
+
exit 1
|
67
|
+
rescue Google::Compute::ParameterValidation
|
68
|
+
ui.error("Must specify zone in knife config file or in command line as an option. Try --help.")
|
66
69
|
exit 1
|
67
70
|
end
|
68
71
|
|
@@ -87,9 +90,8 @@ class Chef
|
|
87
90
|
ui.warn("Deleted server '#{selflink2name(zone)}:#{instance.name}'")
|
88
91
|
|
89
92
|
if config[:purge]
|
90
|
-
|
91
|
-
destroy_item(Chef::
|
92
|
-
destroy_item(Chef::ApiClient, thing_to_delete, "client")
|
93
|
+
destroy_item(Chef::Node, instance.name, "node")
|
94
|
+
destroy_item(Chef::ApiClient, instance.name, "client")
|
93
95
|
else
|
94
96
|
ui.warn("Corresponding node and client for the #{instance.name} server were not deleted and remain registered with the Chef Server")
|
95
97
|
end
|
@@ -25,28 +25,23 @@ class Chef
|
|
25
25
|
option :zone,
|
26
26
|
:short => "-Z ZONE",
|
27
27
|
:long => "--google-compute-zone ZONE",
|
28
|
-
:description => "The Zone for this server"
|
29
|
-
:required => true
|
28
|
+
:description => "The Zone for this server"
|
30
29
|
|
31
30
|
def run
|
32
31
|
$stdout.sync = true
|
33
|
-
|
32
|
+
|
34
33
|
begin
|
35
|
-
zone = client.zones.get(config[:zone])
|
34
|
+
zone = client.zones.get(config[:zone] || Chef::Config[:knife][:google_compute_zone])
|
36
35
|
rescue Google::Compute::ResourceNotFound
|
37
|
-
ui.error("Zone '#{config[:zone]}' not found")
|
36
|
+
ui.error("Zone '#{config[:zone] || Chef::Config[:knife][:google_compute_zone] }' not found.")
|
37
|
+
exit 1
|
38
|
+
rescue Google::Compute::ParameterValidation
|
39
|
+
ui.error("Must specify zone in knife config file or in command line as an option. Try --help.")
|
38
40
|
exit 1
|
39
41
|
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
ui.color('Type', :bold),
|
44
|
-
ui.color('Image', :bold),
|
45
|
-
ui.color('Public IP', :bold),
|
46
|
-
ui.color('Private IP', :bold),
|
47
|
-
ui.color('Disks', :bold),
|
48
|
-
ui.color("Zone", :bold),
|
49
|
-
ui.color('Status', :bold)].flatten.compact
|
43
|
+
instance_label = ['Name', 'Type', 'Image', 'Public IP', 'Private IP', 'Disks', 'Zone', 'Status']
|
44
|
+
instance_list = (instance_label.map {|label| ui.color(label, :bold)}).flatten.compact
|
50
45
|
|
51
46
|
output_column_count = instance_list.length
|
52
47
|
|
@@ -70,7 +65,12 @@ class Chef
|
|
70
65
|
end
|
71
66
|
end
|
72
67
|
end
|
73
|
-
|
68
|
+
|
69
|
+
if instance_list.count > 8 # This condition checks if there are any servers available. The first 8 values are the Labels.
|
70
|
+
puts ui.list(instance_list, :uneven_columns_across, output_column_count)
|
71
|
+
else
|
72
|
+
puts "No servers found in #{zone.name} zone."
|
73
|
+
end
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -76,24 +76,32 @@ module Google
|
|
76
76
|
$stdout.print "\n\nAuthorization code: "
|
77
77
|
authorization_code = $stdin.gets.chomp
|
78
78
|
api_client.authorization.code = authorization_code
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
filepath = File.expand_path(DEFAULT_FILE)
|
79
|
+
|
80
|
+
begin
|
81
|
+
api_client.authorization.fetch_access_token!
|
82
|
+
rescue Faraday::Error::ConnectionFailed => e
|
83
|
+
raise ConnectionFail,
|
84
|
+
"The SSL certificates validation may not configured for this system. Please refer README to configured SSL certificates validation"\
|
85
|
+
if e.message.include? "SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed"
|
87
86
|
else
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
87
|
+
access_token = api_client.authorization.access_token
|
88
|
+
refresh_token = api_client.authorization.refresh_token
|
89
|
+
id_token = api_client.authorization.id_token
|
90
|
+
expires_in = api_client.authorization.expires_in
|
91
|
+
issued_at = api_client.authorization.issued_at.to_s
|
92
|
+
if !@credential_file
|
93
|
+
filepath = File.expand_path(DEFAULT_FILE)
|
94
|
+
else
|
95
|
+
filepath = File.expand_path(@credential_file)
|
96
|
+
end
|
97
|
+
File.open(filepath,'w+') do |f|
|
98
|
+
f.write(MultiJson.dump({"authorization_uri" => authorization_uri,
|
99
|
+
"token_credential_uri"=>"https://accounts.google.com/o/oauth2/token",
|
100
|
+
"scope"=>scope,"redirect_uri"=>redirect_uri, "client_id"=>client_id,
|
101
|
+
"client_secret"=>client_secret, "access_token"=>access_token,
|
102
|
+
"expires_in"=>expires_in,"refresh_token"=> refresh_token, "id_token"=>id_token,
|
103
|
+
"issued_at"=>issued_at,"project"=>project }, :pretty=>true))
|
104
|
+
end
|
97
105
|
end
|
98
106
|
end
|
99
107
|
|
@@ -154,7 +162,7 @@ module Google
|
|
154
162
|
end
|
155
163
|
|
156
164
|
def compute
|
157
|
-
@compute ||= @api_client.discovered_api('compute','
|
165
|
+
@compute ||= @api_client.discovered_api('compute','v1beta15')
|
158
166
|
end
|
159
167
|
|
160
168
|
def dispatch(opts)
|
@@ -33,6 +33,12 @@ module Google
|
|
33
33
|
def insert(options={})
|
34
34
|
create(options)
|
35
35
|
end
|
36
|
+
|
37
|
+
def create_snapshot(options={})
|
38
|
+
data = @dispatcher.dispatch(:api_method => api_resource.create_snapshot, :parameters=>options)
|
39
|
+
ZoneOperation.new(data.merge!(:dispatcher=>@dispatcher)) unless data.nil?
|
40
|
+
end
|
41
|
+
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
@@ -14,6 +14,7 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
+
require 'faraday'
|
17
18
|
module Google
|
18
19
|
module Compute
|
19
20
|
class ParameterValidation < ArgumentError; end
|
@@ -23,6 +24,7 @@ module Google
|
|
23
24
|
class OperationTimeout < RuntimeError; end
|
24
25
|
class HashConvert < RuntimeError; end
|
25
26
|
class ResourceNotFound < RuntimeError; end
|
27
|
+
class ConnectionFail < Faraday::Error::ConnectionFailed; end
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
data/lib/knife-google/version.rb
CHANGED
@@ -26,7 +26,7 @@ describe Chef::Knife::GoogleBase do
|
|
26
26
|
|
27
27
|
it "#selflink2name should return name from a seleflink url" do
|
28
28
|
knife_plugin.selflink2name(
|
29
|
-
'https://www.googleapis.com/compute/
|
29
|
+
'https://www.googleapis.com/compute/v1beta15/projects/mock-project/category/resource').
|
30
30
|
should eq('resource')
|
31
31
|
end
|
32
32
|
|
@@ -16,24 +16,19 @@ require 'spec_helper'
|
|
16
16
|
|
17
17
|
describe Chef::Knife::GoogleServerCreate do
|
18
18
|
|
19
|
-
|
20
|
-
Chef::Knife::GoogleServerCreate.new(["-m"+stored_machine_type.name,
|
21
|
-
"-I"+stored_image.name, "-n"+stored_network.name,
|
22
|
-
"-Z"+stored_zone.name, stored_instance.name])
|
23
|
-
end
|
24
|
-
|
25
|
-
it "#run should invoke compute api to create an server" do
|
19
|
+
before(:each) do
|
26
20
|
zones = mock(Google::Compute::ListableResourceCollection)
|
27
21
|
zones.should_receive(:get).with(stored_zone.name).
|
28
22
|
and_return(stored_zone)
|
29
23
|
|
30
24
|
machine_types = mock(Google::Compute::ListableResourceCollection)
|
31
|
-
machine_types.should_receive(:get).
|
25
|
+
machine_types.should_receive(:get).
|
26
|
+
with({:name=>stored_machine_type.name, :zone=>stored_zone.name}).
|
32
27
|
and_return(stored_machine_type)
|
33
28
|
|
34
29
|
images = mock(Google::Compute::ListableResourceCollection)
|
35
30
|
images.should_receive(:get).
|
36
|
-
with({:project=>"
|
31
|
+
with({:project=>"debian-cloud", :name=>stored_image.name}).
|
37
32
|
and_return(stored_image)
|
38
33
|
|
39
34
|
networks = mock(Google::Compute::ListableResourceCollection)
|
@@ -59,7 +54,13 @@ describe Chef::Knife::GoogleServerCreate do
|
|
59
54
|
:images=>images, :zones=>zones,:machine_types=>machine_types,
|
60
55
|
:networks=>networks)
|
61
56
|
Google::Compute::Client.stub!(:from_json).and_return(client)
|
57
|
+
end
|
62
58
|
|
59
|
+
it "#run should invoke compute api to create an server" do
|
60
|
+
knife_plugin = Chef::Knife::GoogleServerCreate.new(["-m"+stored_machine_type.name,
|
61
|
+
"-I"+stored_image.name, "-J"+"debian-cloud",
|
62
|
+
"-n"+stored_network.name,
|
63
|
+
"-Z"+stored_zone.name, stored_instance.name])
|
63
64
|
knife_plugin.config[:disks]=[]
|
64
65
|
knife_plugin.config[:metadata]=[]
|
65
66
|
knife_plugin.config[:public_ip]='EPHEMERAL'
|
@@ -73,12 +74,31 @@ describe Chef::Knife::GoogleServerCreate do
|
|
73
74
|
knife_plugin.run
|
74
75
|
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
77
|
+
it "should read zone value from knife config file." do
|
78
|
+
Chef::Config[:knife][:google_compute_zone] = stored_zone.name
|
79
|
+
knife_plugin = Chef::Knife::GoogleServerCreate.new(["-m"+stored_machine_type.name,
|
80
|
+
"-I"+stored_image.name, "-J"+"debian-cloud",
|
81
|
+
"-n"+stored_network.name,
|
82
|
+
stored_instance.name])
|
83
|
+
knife_plugin.config[:disks]=[]
|
84
|
+
knife_plugin.config[:metadata]=[]
|
85
|
+
knife_plugin.config[:public_ip]='EPHEMERAL'
|
86
|
+
knife_plugin.ui.stub!(:info)
|
87
|
+
|
88
|
+
knife_plugin.stub!(:wait_for_sshd)
|
89
|
+
knife_plugin.should_receive(:bootstrap_for_node).
|
90
|
+
with(stored_instance,'10.100.0.10').
|
91
|
+
and_return(mock("Chef::Knife::Bootstrap",:run=>true))
|
92
|
+
knife_plugin.run
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "without appropriate command line options" do
|
98
|
+
it "should throw exception when required params are not passed" do
|
99
|
+
$stdout.stub!(:write) # lets not print those error messages
|
100
|
+
expect {
|
101
|
+
Chef::Knife::GoogleServerCreate.new([ "NAME"])
|
102
|
+
}.to raise_error(SystemExit)
|
83
103
|
end
|
84
104
|
end
|
@@ -103,3 +103,25 @@ describe Chef::Knife::GoogleServerDelete do
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
end
|
106
|
+
|
107
|
+
describe Chef::Knife::GoogleServerDelete do
|
108
|
+
it "should read zone value from knife config file." do
|
109
|
+
Chef::Config[:knife][:google_compute_zone] = stored_zone.name
|
110
|
+
knife_plugin = Chef::Knife::GoogleServerDelete.new([stored_instance.name])
|
111
|
+
zones = mock(Google::Compute::ListableResourceCollection)
|
112
|
+
zones.should_receive(:get).with(stored_zone.name).and_return(stored_zone)
|
113
|
+
|
114
|
+
instances = mock(Google::Compute::DeletableResourceCollection)
|
115
|
+
instances.should_receive(:get).with(:name=>stored_instance.name, :zone=>stored_zone.name).
|
116
|
+
and_return(stored_instance)
|
117
|
+
instances.should_receive(:delete).with(:instance=>stored_instance.name, :zone=>stored_zone.name)
|
118
|
+
|
119
|
+
client = mock(Google::Compute::Client, :zones=>zones, :instances=>instances)
|
120
|
+
Google::Compute::Client.stub!(:from_json).and_return(client)
|
121
|
+
knife_plugin.config[:yes] = true
|
122
|
+
knife_plugin.ui.should_receive(:warn).twice
|
123
|
+
knife_plugin.stub!(:msg_pair)
|
124
|
+
knife_plugin.run
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|