knife-google 1.0.0 → 1.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.
Files changed (42) hide show
  1. data/.travis.yml +4 -0
  2. data/README.md +34 -7
  3. data/lib/chef/knife/google_base.rb +1 -0
  4. data/lib/chef/knife/google_server_create.rb +49 -8
  5. data/lib/chef/knife/google_server_delete.rb +9 -7
  6. data/lib/chef/knife/google_server_list.rb +15 -15
  7. data/lib/google/compute/client.rb +26 -18
  8. data/lib/google/compute/creatable_resource_collection.rb +6 -0
  9. data/lib/google/compute/exception.rb +2 -0
  10. data/lib/knife-google/version.rb +1 -1
  11. data/spec/chef/knife/google_base_spec.rb +1 -1
  12. data/spec/chef/knife/google_server_create_spec.rb +36 -16
  13. data/spec/chef/knife/google_server_delete_spec.rb +22 -0
  14. data/spec/chef/knife/google_server_list_spec.rb +13 -14
  15. data/spec/data/{compute-v1beta14.json → compute-v1beta15.json} +4122 -1228
  16. data/spec/data/disk.json +2 -2
  17. data/spec/data/firewall.json +2 -2
  18. data/spec/data/global_operation.json +3 -3
  19. data/spec/data/image.json +2 -2
  20. data/spec/data/kernel.json +1 -1
  21. data/spec/data/machine_type.json +3 -3
  22. data/spec/data/network.json +1 -1
  23. data/spec/data/project.json +1 -1
  24. data/spec/data/serial_port_output.json +1 -1
  25. data/spec/data/server.json +6 -6
  26. data/spec/data/snapshot.json +2 -2
  27. data/spec/data/zone.json +1 -1
  28. data/spec/data/zone_operation.json +3 -3
  29. data/spec/google/compute/disk_spec.rb +17 -7
  30. data/spec/google/compute/firewall_spec.rb +27 -26
  31. data/spec/google/compute/global_operation_spec.rb +6 -6
  32. data/spec/google/compute/image_spec.rb +11 -11
  33. data/spec/google/compute/kernel_spec.rb +4 -4
  34. data/spec/google/compute/machine_type_spec.rb +4 -4
  35. data/spec/google/compute/network_spec.rb +9 -9
  36. data/spec/google/compute/project_spec.rb +14 -14
  37. data/spec/google/compute/server_spec.rb +23 -23
  38. data/spec/google/compute/snapshot_spec.rb +6 -18
  39. data/spec/google/compute/zone_operation_spec.rb +6 -6
  40. data/spec/google/compute/zone_spec.rb +5 -4
  41. data/spec/support/mocks.rb +2 -2
  42. metadata +112 -117
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.9.3
3
+
4
+ script: bundle exec rake spec
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 centos-6-v20130325 -Z us-central2-a -i ~/.ssh/id_rsa -x jdoe
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. Note that if you are bootstrapping the node, make sure to
208
- follow the preparation instructions earlier and use the `-x` and
209
- `-i` commands to specify the username and the identity file for
210
- that user. Make sure to use the private key file (e.g. `~/.ssh/id_rsa`)
211
- for the identity file and *not* the public key file.
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`.
@@ -66,6 +66,7 @@ class Chef
66
66
  }
67
67
  }.flatten.compact
68
68
  end
69
+
69
70
  end
70
71
  end
71
72
  end
@@ -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
- image = client.images.get(:project=>'google', :name=>config[:image]).self_link
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
- ui.error("Image '#{config[:image]}' not found")
283
- exit 1
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
- thing_to_delete = config[:chef_node_name] || instance.name
91
- destroy_item(Chef::Node, thing_to_delete, "node")
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
- instance_list = [
42
- ui.color("Name", :bold),
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
- puts ui.list(instance_list, :uneven_columns_across, output_column_count)
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
- api_client.authorization.fetch_access_token!
80
- access_token = api_client.authorization.access_token
81
- refresh_token = api_client.authorization.refresh_token
82
- id_token = api_client.authorization.id_token
83
- expires_in = api_client.authorization.expires_in
84
- issued_at = api_client.authorization.issued_at.to_s
85
- if !@credential_file
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
- filepath = File.expand_path(@credential_file)
89
- end
90
- File.open(filepath,'w+') do |f|
91
- f.write(MultiJson.dump({"authorization_uri" => authorization_uri,
92
- "token_credential_uri"=>"https://accounts.google.com/o/oauth2/token",
93
- "scope"=>scope,"redirect_uri"=>redirect_uri, "client_id"=>client_id,
94
- "client_secret"=>client_secret, "access_token"=>access_token,
95
- "expires_in"=>expires_in,"refresh_token"=> refresh_token, "id_token"=>id_token,
96
- "issued_at"=>issued_at,"project"=>project }, :pretty=>true))
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','v1beta14')
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
 
@@ -14,6 +14,6 @@
14
14
  #
15
15
  module Knife
16
16
  module Google
17
- VERSION = "1.0.0"
17
+ VERSION = "1.1.0"
18
18
  end
19
19
  end
@@ -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/v1beta14/projects/mock-project/category/resource').
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
- let(:knife_plugin) do
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).with(stored_machine_type.name).
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=>"google", :name=>stored_image.name}).
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
- describe "without appropriate command line options" do
77
- it "should throw exception when required params are not passed" do
78
- $stdout.stub!(:write) # lets not print those error messages
79
- expect {
80
- Chef::Knife::GoogleServerCreate.new([ "NAME"])
81
- }.to raise_error(SystemExit)
82
- end
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