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.
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