knife-google 0.0.1 → 1.0.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 (85) hide show
  1. data/.gitignore +22 -0
  2. data/CONTRIB.md +64 -0
  3. data/Gemfile +11 -0
  4. data/README.md +287 -0
  5. data/Rakefile +53 -0
  6. data/knife-google.gemspec +26 -0
  7. data/lib/chef/knife/google_base.rb +39 -68
  8. data/lib/chef/knife/google_disk_create.rb +60 -0
  9. data/lib/chef/knife/google_disk_delete.rb +60 -0
  10. data/lib/chef/knife/google_disk_list.rb +75 -0
  11. data/lib/chef/knife/google_server_create.rb +273 -184
  12. data/lib/chef/knife/google_server_delete.rb +74 -32
  13. data/lib/chef/knife/google_server_list.rb +45 -64
  14. data/lib/chef/knife/google_setup.rb +31 -0
  15. data/lib/chef/knife/google_zone_list.rb +78 -0
  16. data/lib/google/compute.rb +46 -0
  17. data/lib/google/compute/client.rb +188 -0
  18. data/lib/google/compute/config.rb +23 -0
  19. data/lib/google/compute/creatable_resource_collection.rb +38 -0
  20. data/lib/google/compute/deletable_resource_collection.rb +51 -0
  21. data/lib/google/compute/disk.rb +40 -0
  22. data/lib/google/compute/exception.rb +28 -0
  23. data/lib/google/compute/firewall.rb +65 -0
  24. data/lib/google/compute/global_operation.rb +60 -0
  25. data/lib/google/compute/image.rb +30 -0
  26. data/lib/google/compute/kernel.rb +20 -0
  27. data/lib/google/compute/listable_resource_collection.rb +33 -0
  28. data/lib/google/compute/machine_type.rb +36 -0
  29. data/lib/google/compute/mixins/utils.rb +58 -0
  30. data/lib/google/compute/network.rb +29 -0
  31. data/lib/google/compute/project.rb +76 -0
  32. data/lib/google/compute/resource.rb +81 -0
  33. data/lib/google/compute/resource_collection.rb +78 -0
  34. data/lib/google/compute/server.rb +87 -0
  35. data/lib/google/compute/server/attached_disk.rb +39 -0
  36. data/lib/google/compute/server/network_interface.rb +38 -0
  37. data/lib/google/compute/server/network_interface/access_config.rb +35 -0
  38. data/lib/google/compute/server/serial_port_output.rb +31 -0
  39. data/lib/google/compute/snapshot.rb +30 -0
  40. data/lib/google/compute/version.rb +19 -0
  41. data/lib/google/compute/zone.rb +32 -0
  42. data/lib/google/compute/zone_operation.rb +60 -0
  43. data/lib/knife-google/version.rb +18 -2
  44. data/spec/chef/knife/google_base_spec.rb +46 -0
  45. data/spec/chef/knife/google_disk_create_spec.rb +36 -0
  46. data/spec/chef/knife/google_disk_delete_spec.rb +65 -0
  47. data/spec/chef/knife/google_disk_list_spec.rb +36 -0
  48. data/spec/chef/knife/google_server_create_spec.rb +84 -0
  49. data/spec/chef/knife/google_server_delete_spec.rb +105 -0
  50. data/spec/chef/knife/google_server_list_spec.rb +39 -0
  51. data/spec/chef/knife/google_setup_spec.rb +25 -0
  52. data/spec/chef/knife/google_zone_list_spec.rb +32 -0
  53. data/spec/data/client.json +14 -0
  54. data/spec/data/compute-v1beta14.json +3386 -0
  55. data/spec/data/disk.json +15 -0
  56. data/spec/data/firewall.json +13 -0
  57. data/spec/data/global_operation.json +36 -0
  58. data/spec/data/image.json +12 -0
  59. data/spec/data/kernel.json +15 -0
  60. data/spec/data/machine_type.json +24 -0
  61. data/spec/data/network.json +10 -0
  62. data/spec/data/project.json +21 -0
  63. data/spec/data/serial_port_output.json +5 -0
  64. data/spec/data/server.json +46 -0
  65. data/spec/data/snapshot.json +12 -0
  66. data/spec/data/zone.json +30 -0
  67. data/spec/data/zone_operation.json +36 -0
  68. data/spec/google/compute/disk_spec.rb +105 -0
  69. data/spec/google/compute/firewall_spec.rb +128 -0
  70. data/spec/google/compute/global_operation_spec.rb +62 -0
  71. data/spec/google/compute/image_spec.rb +75 -0
  72. data/spec/google/compute/kernel_spec.rb +49 -0
  73. data/spec/google/compute/machine_type_spec.rb +53 -0
  74. data/spec/google/compute/network_spec.rb +68 -0
  75. data/spec/google/compute/project_spec.rb +71 -0
  76. data/spec/google/compute/server_spec.rb +125 -0
  77. data/spec/google/compute/snapshot_spec.rb +69 -0
  78. data/spec/google/compute/zone_operation_spec.rb +62 -0
  79. data/spec/google/compute/zone_spec.rb +50 -0
  80. data/spec/spec_helper.rb +44 -0
  81. data/spec/support/mocks.rb +62 -0
  82. data/spec/support/resource_examples.rb +70 -0
  83. data/spec/support/spec_google_base.rb +56 -0
  84. metadata +121 -31
  85. data/README.rdoc +0 -96
@@ -1,99 +1,70 @@
1
- #
2
- # Author:: Chirag Jog (<chiragj@websym.com>)
3
- # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
- # License:: Apache License, Version 2.0
1
+ # Copyright 2013 Google Inc. All Rights Reserved.
5
2
  #
6
3
  # Licensed under the Apache License, Version 2.0 (the "License");
7
4
  # you may not use this file except in compliance with the License.
8
5
  # You may obtain a copy of the License at
9
- #
6
+ #
10
7
  # http://www.apache.org/licenses/LICENSE-2.0
11
- #
8
+ #
12
9
  # Unless required by applicable law or agreed to in writing, software
13
10
  # distributed under the License is distributed on an "AS IS" BASIS,
14
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
12
  # See the License for the specific language governing permissions and
16
13
  # limitations under the License.
17
- #
18
14
 
19
- require 'stringio'
20
- require 'yajl'
21
- require 'mixlib/shellout'
15
+ require 'chef/knife'
16
+ require 'google/compute'
22
17
 
23
- $CLI_PREFIX='gcutil'
24
18
  class Chef
25
19
  class Knife
26
20
  module GoogleBase
27
- @parser = Yajl::Parser.new
28
- @gcompute = nil
29
- @cygwin_path = nil
30
21
 
31
- def is_platform_windows?
32
- return RUBY_PLATFORM.scan('w32').size > 0
33
- end
22
+ # hack for mixlib-cli workaround
23
+ # https://github.com/opscode/knife-ec2/blob/master/lib/chef/knife/ec2_base.rb
24
+ def self.included(includer)
25
+ includer.class_eval do
26
+ deps do
27
+ require 'google/compute'
28
+ require 'chef/json_compat'
29
+ end
34
30
 
35
- def is_cygwin_installed?
36
- ENV['CYGWINPATH'] != nil
31
+ option :compute_credential_file,
32
+ :short => "-f CREDENTIAL_FILE",
33
+ :long => "--compute-credential-file CREDENTIAL_FILE",
34
+ :description => "Google Compute credential file (google setup can create this)"
35
+ end
37
36
  end
38
37
 
39
- def gcompute
40
- return if @gcompute
41
- if is_platform_windows?
42
- if is_cygwin_installed?
43
- #Remove extra quotes
44
- @cygwin_path = ENV['CYGWINPATH'].chomp('\'').reverse.chomp('\'').reverse
45
- #FIXME Generalize the python binary
46
- @gcompute="#{@cygwin_path}\\bin\\python2.6.exe #{@cygwin_path}\\bin\\#{$CLI_PREFIX}"
47
- else
48
- puts "Cannot Find Cygwin Installation !!! Please set CYGWINPATH to point to the Cygwin installation"
49
- exit 1
50
- end
51
- else
52
- Chef::Log.debug("Linux Environment")
53
- @gcompute = $CLI_PREFIX
38
+ def client
39
+ @client ||= begin
40
+ Google::Compute::Client.from_json(config[:compute_credential_file])
54
41
  end
55
42
  end
56
43
 
57
- def parser
58
- if @parser.nil?
59
- @parser = Yajl::Parser.new
44
+ def selflink2name(selflink)
45
+ selflink.to_s == '' ? selflink.to_s : selflink.split('/').last
46
+ end
47
+
48
+ def msg_pair(label, value, color=:cyan)
49
+ if value && !value.to_s.empty?
50
+ ui.info("#{ui.color(label, color)}: #{value}")
60
51
  end
61
52
  end
62
-
63
- def to_json(data)
64
- data_s = StringIO::new(data.strip)
65
- parser.parse(data_s) {|obj| return obj}
53
+
54
+ def disks(instance)
55
+ instance.disks.collect{|d|d.device_name}.compact
66
56
  end
67
57
 
68
- def exec_shell_cmd(cmd)
69
- if is_platform_windows? and is_cygwin_installed?
70
- #Change the HOME PATH From Windows to Cygwin
71
- cygwin_home = "#{@cygwin_path}\\home\\#{ENV['USER']}"
72
-
73
- #Auth token should exist in either ENV['HOME'] or cygwin_home
74
- #XXX Find a way to remove the hard-coded file name
75
- if not File.file?("#{ENV['HOME']}\\.#{$CLI_PREFIX}_auth")
76
- ENV['HOME'] = cygwin_home
77
- end
78
- end
79
- shell_cmd = Mixlib::ShellOut.new(cmd)
80
- shell_cmd.run_command
58
+ def private_ips(instance)
59
+ instance.network_interfaces.collect{|ni|ni.network_ip}.compact
81
60
  end
82
61
 
83
- def validate_project(project_id)
84
- cmd = "#{gcompute} getproject --project_id=#{project_id}"
85
- Chef::Log.debug 'Executing ' + cmd
86
- getprj = exec_shell_cmd(cmd)
87
- if getprj.status.exitstatus > 0
88
- if not getprj.stdout.scan("Enter verification code").empty?
89
- ui.error("Failed to authenticate the account")
90
- ui.error("If not authenticated, please Authenticate gcompute to access the Google Compute Cloud")
91
- ui.error("Authenticate by executing gcutil auth --project_id=<project_id>")
92
- exit 1
93
- end
94
- ui.error("#{getprj.stderr}")
95
- exit 1
96
- end
62
+ def public_ips(instance)
63
+ instance.network_interfaces.collect{|ni|
64
+ ni.access_configs.map{|ac|
65
+ ac.nat_ip
66
+ }
67
+ }.flatten.compact
97
68
  end
98
69
  end
99
70
  end
@@ -0,0 +1,60 @@
1
+ # Copyright 2013 Google Inc. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ require 'chef/knife/google_base'
16
+
17
+ class Chef
18
+ class Knife
19
+ class GoogleDiskCreate < Knife
20
+
21
+ include Knife::GoogleBase
22
+
23
+ banner "knife google disk create NAME --google-disk-sizeGb N --google-compute-zone ZONE (options)"
24
+
25
+ deps do
26
+ require 'google/compute'
27
+ end
28
+
29
+ option :zone,
30
+ :short => "-Z ZONE",
31
+ :long => "--google-compute-zone ZONE",
32
+ :description => "The Zone for this disk",
33
+ :required => true
34
+
35
+ option :sizeGb,
36
+ :short => "-s SIZE",
37
+ :long => "--google-disk-sizeGb SIZE",
38
+ :description => "Disk size in GB",
39
+ :required => true
40
+
41
+ def run
42
+ $stdout.sync = true
43
+ unless @name_args.size > 0
44
+ ui.error("Please provide the name of the new disk")
45
+ exit 1
46
+ end
47
+
48
+ begin
49
+ zone = client.zones.get(config[:zone])
50
+ rescue Google::Compute::ResourceNotFound
51
+ ui.error("Zone '#{config[:zone]}' not found")
52
+ exit 1
53
+ end
54
+
55
+ zone_operation = client.disks.create(:name=>@name_args.first,
56
+ :sizeGb=>config[:sizeGb], :zone=>zone.name)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright 2013 Google Inc. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ require 'chef/knife/google_base'
16
+
17
+ class Chef
18
+ class Knife
19
+ class GoogleDiskDelete < Knife
20
+
21
+ include Knife::GoogleBase
22
+
23
+ banner "knife google disk delete NAME --google-compute-zone ZONE"
24
+
25
+ deps do
26
+ require 'google/compute'
27
+ end
28
+
29
+ option :zone,
30
+ :short => "-Z ZONE",
31
+ :long => "--google-compute-zone ZONE",
32
+ :description => "The Zone for this disk",
33
+ :required => true
34
+
35
+ def run
36
+ unless @name_args.size > 0
37
+ ui.error("Please provide the name of the disk to be deleted")
38
+ exit 1
39
+ end
40
+
41
+ begin
42
+ zone = client.zones.get(config[:zone])
43
+ rescue Google::Compute::ResourceNotFound
44
+ ui.error("Zone '#{config[:zone]}' not found")
45
+ exit 1
46
+ end
47
+
48
+ begin
49
+ disk = client.disks.get(:zone=>zone.name, :disk=>@name_args.first)
50
+ ui.confirm("Are you sure you want to delete disk '#{zone.name}:#{disk.name}'")
51
+ zone_operation = client.disks.delete(:zone=>zone.name, :disk=>disk.name)
52
+ ui.warn("Disk '#{zone.name}:#{disk.name}' deleted")
53
+ rescue Google::Compute::ResourceNotFound
54
+ ui.error("Disk '#{zone.name}:#{@name_args.first}' not found")
55
+ exit 1
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,75 @@
1
+ # Copyright 2013 Google Inc. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ require 'chef/knife/google_base'
16
+
17
+ class Chef
18
+ class Knife
19
+ class GoogleDiskList < Knife
20
+
21
+ include Knife::GoogleBase
22
+
23
+ banner "knife google disk list --google-compute-zone ZONE (options)"
24
+
25
+ option :zone,
26
+ :short => "-Z ZONE",
27
+ :long => "--google-compute-zone ZONE",
28
+ :description => "The Zone for disk listing",
29
+ :required => true
30
+
31
+ def run
32
+ $stdout.sync = true
33
+
34
+ begin
35
+ zone = client.zones.get(config[:zone])
36
+ rescue Google::Compute::ResourceNotFound
37
+ ui.error("Zone '#{config[:zone]}' not found")
38
+ exit 1
39
+ end
40
+
41
+ disk_list = [
42
+ ui.color("Name", :bold),
43
+ ui.color('Zone', :bold),
44
+ ui.color('Source Snapshot', :bold),
45
+ ui.color('Size (In GB)', :bold),
46
+ ui.color('Status', :bold)].flatten.compact
47
+
48
+ output_column_count = disk_list.length
49
+
50
+ client.disks.list(:zone=>zone.name).each do |disk|
51
+ disk_list << disk.name
52
+ disk_list << selflink2name(disk.zone)
53
+ if disk.source_snapshot.nil?
54
+ disk_list << " "
55
+ else
56
+ selflink2name(disk.source_snapshot)
57
+ end
58
+ disk_list << disk.size_gb
59
+ disk_list << begin
60
+ status = disk.status.downcase
61
+ case status
62
+ when 'stopping', 'stopped', 'terminated'
63
+ ui.color(status, :red)
64
+ when 'requested', 'provisioning', 'staging'
65
+ ui.color(status, :yellow)
66
+ else
67
+ ui.color(status, :green)
68
+ end
69
+ end
70
+ end
71
+ ui.info(ui.list(disk_list, :uneven_columns_across, output_column_count))
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,150 +1,177 @@
1
- # Author:: Chirag Jog (<chiragj@websym.com>)
2
- # Copyright:: Copyright (c) 2012 Opscode, Inc.
3
- # License:: Apache License, Version 2.0
1
+ # Copyright 2013 Google Inc. All Rights Reserved.
4
2
  #
5
3
  # Licensed under the Apache License, Version 2.0 (the "License");
6
4
  # you may not use this file except in compliance with the License.
7
5
  # You may obtain a copy of the License at
8
- #
6
+ #
9
7
  # http://www.apache.org/licenses/LICENSE-2.0
10
- #
8
+ #
11
9
  # Unless required by applicable law or agreed to in writing, software
12
10
  # distributed under the License is distributed on an "AS IS" BASIS,
13
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
12
  # See the License for the specific language governing permissions and
15
13
  # limitations under the License.
16
-
17
- require 'highline'
18
- require 'net/ssh/multi'
19
- require 'net/scp'
20
- require 'tempfile'
21
-
22
- require 'chef/knife'
14
+ #
23
15
  require 'chef/knife/google_base'
24
16
 
25
17
  class Chef
26
18
  class Knife
27
19
  class GoogleServerCreate < Knife
28
20
 
21
+ include Knife::GoogleBase
22
+
29
23
  deps do
30
- require 'readline'
24
+ require 'google/compute'
31
25
  require 'chef/json_compat'
32
26
  require 'chef/knife/bootstrap'
33
27
  Chef::Knife::Bootstrap.load_deps
34
28
  end
35
29
 
36
- include Knife::GoogleBase
30
+ banner "knife google server create NAME -m MACHINE_TYPE -I IMAGE -Z ZONE (options)"
37
31
 
38
- banner "knife google server create NAME [RUN LIST...] (options)"
32
+ attr_accessor :initial_sleep_delay
33
+ attr_reader :instance
39
34
 
40
- option :run_list,
41
- :short => "-r RUN_LIST",
42
- :long => "--run-list RUN_LIST",
43
- :description => "Comma separated list of roles/recipes to apply",
44
- :proc => lambda { |o| o.split(/[\s,]+/) },
45
- :default => []
35
+ option :machine_type,
36
+ :short => "-m MACHINE_TYPE",
37
+ :long => "--google-compute-machine MACHINE_TYPE",
38
+ :description => "The machine type of server (n1-highcpu-2, n1-highcpu-2-d, etc)",
39
+ :required => true
40
+
41
+ option :image,
42
+ :short => "-I IMAGE",
43
+ :long => "--google-compute-image IMAGE",
44
+ :description => "The Image for the server",
45
+ :required => true
46
46
 
47
- option :availability_zone,
47
+ option :zone,
48
48
  :short => "-Z ZONE",
49
- :long => "--availability-zone ZONE",
50
- :description => "The Availability Zone",
51
- :default => "us-east-a",
52
- :proc => Proc.new { |key| Chef::Config[:knife][:availability_zone] = key }
49
+ :long => "--google-compute-zone ZONE",
50
+ :description => "The Zone for this server",
51
+ :required => true
53
52
 
54
- option :distro,
55
- :short => "-d DISTRO",
56
- :long => "--distro DISTRO",
57
- :description => "Bootstrap a distro using a template; default is 'ubuntu10.04-gems'",
58
- :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
59
- :default => "ubuntu10.04-gems"
53
+ option :network,
54
+ :short => "-n NETWORK",
55
+ :long => "--google-compute-network NETWORK",
56
+ :description => "The network for this server; default is 'default'",
57
+ :default => "default"
60
58
 
61
- option :template_file,
62
- :long => "--template-file TEMPLATE",
63
- :description => "Full path to location of template to use",
64
- :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
65
- :default => false
59
+ option :tags,
60
+ :short => "-T TAG1,TAG2,TAG3",
61
+ :long => "--google-compute-tags TAG1,TAG2,TAG3",
62
+ :description => "Tags for this server",
63
+ :proc => Proc.new { |tags| tags.split(',') },
64
+ :default => []
65
+
66
+ option :metadata,
67
+ :short => "-M K=V[,K=V,...]",
68
+ :long => "--google-compute-metadata Key=Value[,Key=Value...]",
69
+ :description => "The metadata for this server",
70
+ :proc => Proc.new { |metadata| metadata.split(',') },
71
+ :default => []
66
72
 
67
73
  option :chef_node_name,
68
74
  :short => "-N NAME",
69
75
  :long => "--node-name NAME",
70
- :description => "The Chef node name for your new node",
71
- :proc => Proc.new { |t| Chef::Config[:knife][:chef_node_name] = t }
76
+ :description => "The Chef node name for your new node"
72
77
 
73
78
  option :ssh_user,
74
79
  :short => "-x USERNAME",
75
80
  :long => "--ssh-user USERNAME",
76
- :description => "The ssh username; default is 'ubuntu'",
77
- :default => "ubuntu"
81
+ :description => "The ssh username; default is 'root'",
82
+ :default => "root"
83
+
84
+ option :ssh_password,
85
+ :short => "-P PASSWORD",
86
+ :long => "--ssh-password PASSWORD",
87
+ :description => "The ssh password"
88
+
89
+ option :ssh_port,
90
+ :short => "-p PORT",
91
+ :long => "--ssh-port PORT",
92
+ :description => "The ssh port; default is '22'",
93
+ :default => "22"
94
+
95
+ option :ssh_gateway,
96
+ :short => "-w GATEWAY",
97
+ :long => "--ssh-gateway GATEWAY",
98
+ :description => "The ssh gateway server"
99
+
100
+ option :identity_file,
101
+ :short => "-i IDENTITY_FILE",
102
+ :long => "--identity-file IDENTITY_FILE",
103
+ :description => "The SSH identity file used for authentication"
104
+
105
+ option :prerelease,
106
+ :long => "--prerelease",
107
+ :description => "Install the pre-release chef gems"
108
+
109
+ option :bootstrap_version,
110
+ :long => "--bootstrap-version VERSION",
111
+ :description => "The version of Chef to install"
78
112
 
79
- option :server_name,
80
- :short => "-N NAME",
81
- :long => "--server-name NAME",
82
- :description => "The server name",
83
- :proc => Proc.new { |server_name| Chef::Config[:knife][:server_name] = server_name }
113
+ option :distro,
114
+ :short => "-d DISTRO",
115
+ :long => "--distro DISTRO",
116
+ :description => "Bootstrap a distro using a template; default is 'chef-full'",
117
+ :default => 'chef-full'
84
118
 
85
- option :flavor,
86
- :short => "-f FLAVOR",
87
- :long => "--flavor FLAVOR",
88
- :description => "The flavor of server (standard-1-cpu,standard-2-cpu-ephemeral-disk, etc)",
89
- :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f },
90
- :default => "standard-1-cpu"
119
+ option :template_file,
120
+ :long => "--template-file TEMPLATE",
121
+ :description => "Full path to location of template to use",
122
+ :default => false
91
123
 
92
- option :image,
93
- :short => "-I IMAGE",
94
- :long => "--google-image IMAGE",
95
- :description => "Your google virtual app template/image name",
96
- :proc => Proc.new { |template| Chef::Config[:knife][:image] = template },
97
- :default => "gcompute8-standard"
98
-
99
- option :private_key_file,
100
- :short => "-i PRIVATE_KEY_FILE",
101
- :long => "--private-key-file PRIVATE_KEY_FILE",
102
- :description => "The SSH private key file used for authentication",
103
- :proc => Proc.new { |identity| Chef::Config[:knife][:private_key_file] = identity }
104
-
105
- option :public_key_file,
106
- :short => "-k PUBLIC_KEY_FILE",
107
- :long => "--public-key-file PUBLIC_KEY_FILE",
108
- :description => "The SSH public key file used for authentication",
109
- :proc => Proc.new { |identity| Chef::Config[:knife][:public_key_file] = identity }
110
-
111
- option :network,
112
- :short => "-n NETWORKNAME",
113
- :long => "--network NETWORKNAME",
114
- :description => "The Network in which to create the Virtual machine",
115
- :proc => Proc.new { |network| Chef::Config[:knife][:network] = network},
116
- :default => "default"
124
+ option :run_list,
125
+ :short => "-r RUN_LIST",
126
+ :long => "--run-list RUN_LIST",
127
+ :description => "Comma separated list of roles/recipes to apply",
128
+ :proc => lambda { |o| o.split(/[\s,]+/) }
129
+
130
+ option :json_attributes,
131
+ :short => "-j JSON",
132
+ :long => "--json-attributes JSON",
133
+ :description => "A JSON string to be added to the first run of chef-client",
134
+ :proc => lambda { |o| JSON.parse(o) }
135
+
136
+ option :host_key_verify,
137
+ :long => "--[no-]host-key-verify",
138
+ :description => "Verify host key, enabled by default.",
139
+ :boolean => true,
140
+ :default => true
141
+
142
+ option :compute_user_data,
143
+ :long => "--user-data USER_DATA_FILE",
144
+ :short => "-u USER_DATA_FILE",
145
+ :description => "The Google Compute User Data file to provision the server with"
146
+
147
+ option :hint,
148
+ :long => "--hint HINT_NAME[=HINT_FILE]",
149
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
150
+ :proc => Proc.new { |h|
151
+ Chef::Config[:knife][:hints] ||= {}
152
+ name, path = h.split("=")
153
+ Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
154
+ }
155
+
156
+ option :instance_connect_ip,
157
+ :long => "--google-compute-server-connect-ip PUBLIC",
158
+ :short => "-a PUBLIC",
159
+ :description => "Whether to use PUBLIC or PRIVATE address to connect; default is 'PUBLIC'",
160
+ :default => 'PUBLIC'
161
+
162
+ option :disks,
163
+ :long=> "--google-compute-disks DISK1,DISK2",
164
+ :proc => Proc.new { |metadata| metadata.split(',') },
165
+ :description => "Disks to be attached",
166
+ :default => []
117
167
 
118
- option :external_ip_address,
119
- :short => "-e IPADDRESS",
120
- :long => "--external-ip-address IPADDRESS",
121
- :description => "A Static IP provided by Google",
122
- :proc => Proc.new { |ipaddr| Chef::Config[:knife][:external_ip_address] = ipaddr},
123
- :default => "ephemeral"
124
-
125
- option :internal_ip_address,
126
- :short => "-P IPADDRESS",
127
- :long => "--internal-ip-address IPADDRESS",
128
- :description => "A Static IP provided by Google",
129
- :proc => Proc.new { |ipaddr| Chef::Config[:knife][:internal_ip_address] = ipaddr}
130
-
131
- option :project,
132
- :short => "-p PROJECT",
133
- :long => "--project_id PROJECT",
134
- :description => "Google Compute Project",
135
- :proc => Proc.new { |project| Chef::Config[:knife][:google_project] = project}
136
-
137
- def h
138
- @highline ||= HighLine.new
139
- end
140
-
141
- def locate_config_value(key)
142
- key = key.to_sym
143
- config[key] || Chef::Config[:knife][key]
144
- end
168
+ option :public_ip,
169
+ :long=> "--google-compute-public-ip IP_ADDRESS",
170
+ :description => "EPHEMERAL or static IP address or NONE; default is 'EPHEMERAL'",
171
+ :default => "EPHEMERAL"
145
172
 
146
- def tcp_test_ssh(hostname, port)
147
- tcp_socket = TCPSocket.new(hostname, port)
173
+ def tcp_test_ssh(hostname, ssh_port)
174
+ tcp_socket = TCPSocket.new(hostname, ssh_port)
148
175
  readable = IO.select([tcp_socket], nil, nil, 5)
149
176
  if readable
150
177
  Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
@@ -153,108 +180,170 @@ class Chef
153
180
  else
154
181
  false
155
182
  end
156
- rescue Errno::ETIMEDOUT
157
- false
158
- rescue Errno::EPERM
159
- false
160
- rescue Errno::ECONNREFUSED
183
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
161
184
  sleep 2
162
185
  false
163
- rescue Errno::EHOSTUNREACH
164
- sleep 2
186
+ rescue Errno::EPERM, Errno::ETIMEDOUT
165
187
  false
166
188
  ensure
167
189
  tcp_socket && tcp_socket.close
168
190
  end
169
191
 
170
- def run
171
- unless Chef::Config[:knife][:server_name]
172
- ui.error("Server Name is a compulsory parameter")
173
- exit 1
174
- end
192
+ def wait_for_sshd(hostname)
193
+ config[:ssh_gateway] ? wait_for_tunnelled_sshd(hostname) : wait_for_direct_sshd(hostname, config[:ssh_port])
194
+ end
175
195
 
176
- unless Chef::Config[:knife][:public_key_file]
177
- ui.error("SSH public key file is a compulsory parameter")
178
- exit 1
196
+ def wait_for_tunnelled_sshd(hostname)
197
+ print(".")
198
+ print(".") until tunnel_test_ssh(ssh_connect_host) {
199
+ sleep @initial_sleep_delay ||= 40
200
+ puts("done")
201
+ }
202
+ end
203
+
204
+ def tunnel_test_ssh(hostname, &block)
205
+ gw_host, gw_user = config[:ssh_gateway].split('@').reverse
206
+ gw_host, gw_port = gw_host.split(':')
207
+ gateway = Net::SSH::Gateway.new(gw_host, gw_user, :port => gw_port || 22)
208
+ status = false
209
+ gateway.open(hostname, config[:ssh_port]) do |local_tunnel_port|
210
+ status = tcp_test_ssh('localhost', local_tunnel_port, &block)
179
211
  end
212
+ status
213
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
214
+ sleep 2
215
+ false
216
+ rescue Errno::EPERM, Errno::ETIMEDOUT
217
+ false
218
+ end
180
219
 
181
- unless Chef::Config[:knife][:google_project]
182
- ui.error("Project ID is a compulsory parameter")
183
- exit 1
220
+ def wait_for_direct_sshd(hostname, ssh_port)
221
+ print(".") until tcp_test_ssh(ssh_connect_host, ssh_port) {
222
+ sleep @initial_sleep_delay ||= 40
223
+ puts("done")
224
+ }
225
+ end
226
+
227
+ def ssh_connect_host
228
+ @ssh_connect_host ||= if config[:instance_connect_ip] == 'PUBLIC'
229
+ public_ips(@instance).first
230
+ else
231
+ private_ips(@instance).first
184
232
  end
233
+ end
185
234
 
235
+ def bootstrap_for_node(instance,ssh_host)
236
+ bootstrap = Chef::Knife::Bootstrap.new
237
+ bootstrap.name_args = [ssh_host]
238
+ bootstrap.config[:run_list] = config[:run_list]
239
+ bootstrap.config[:ssh_user] = config[:ssh_user]
240
+ bootstrap.config[:ssh_port] = config[:ssh_port]
241
+ bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
242
+ bootstrap.config[:identity_file] = config[:identity_file]
243
+ bootstrap.config[:chef_node_name] = config[:chef_node_name] || instance.name
244
+ bootstrap.config[:prerelease] = config[:prerelease]
245
+ bootstrap.config[:bootstrap_version] = config[:bootstrap_version]
246
+ bootstrap.config[:first_boot_attributes] = config[:json_attributes]
247
+ bootstrap.config[:distro] = config[:distro]
248
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
249
+ bootstrap.config[:template_file] = config[:template_file]
250
+ bootstrap.config[:environment] = config[:environment]
251
+ # may be needed for vpc_mode
252
+ bootstrap.config[:host_key_verify] = config[:host_key_verify]
253
+ # Modify global configuration state to ensure hint gets set by
254
+ # knife-bootstrap
255
+ Chef::Config[:knife][:hints] ||= {}
256
+ Chef::Config[:knife][:hints]["google"] ||= {}
257
+ bootstrap
258
+ end
259
+
260
+ def run
186
261
  $stdout.sync = true
262
+ unless @name_args.size > 0
263
+ ui.error("Please provide the name of the new server")
264
+ exit 1
265
+ end
187
266
 
188
- project_id = Chef::Config[:knife][:google_project]
189
- validate_project(project_id)
190
-
191
- server_name = Chef::Config[:knife][:server_name]
192
- image = Chef::Config[:knife][:image]
193
- key_file = locate_config_value(:public_key_file)
194
- network = locate_config_value(:network)
195
- flavor = locate_config_value(:flavor)
196
- zone = locate_config_value(:availability_zone)
197
- user = locate_config_value(:ssh_user)
198
- external_ip_address = locate_config_value(:external_ip_address)
199
- internal_ip_address = locate_config_value(:internal_ip_address) || nil
200
- puts "\n#{ui.color("Waiting for the server to be Instantiated", :magenta)}"
201
- cmd_add_instance = "#{@gcompute} addinstance #{server_name} --machine_type #{flavor} " +
202
- "--zone #{zone} --project_id #{project_id} --tags #{server_name} " +
203
- "--authorized_ssh_keys #{user}:#{key_file} --network #{network} " +
204
- "--external_ip_address #{external_ip_address} --print_json"
205
- cmd_add_instance << " --internal_ip_address #{internal_ip_address}" if internal_ip_address
206
-
207
- Chef::Log.debug 'Executing ' + cmd_add_instance
208
- create_server = exec_shell_cmd(cmd_add_instance)
209
-
210
- if create_server.stderr.downcase.scan("error").size > 0
211
- ui.error("\nFailed to create server: #{create_server.stderr}")
267
+ begin
268
+ zone = client.zones.get(config[:zone]).self_link
269
+ rescue Google::Compute::ResourceNotFound
270
+ ui.error("Zone '#{config[:zone]}' not found")
212
271
  exit 1
213
272
  end
214
- if create_server.stdout.downcase.scan("error").size > 0
215
- output = to_json(create_server.stdout)
216
- ui.error("\nFailed to create server: #{output["error"]}")
273
+ begin
274
+ machine_type = client.machine_types.get(config[:machine_type]).self_link
275
+ rescue Google::Compute::ResourceNotFound
276
+ ui.error("MachineType '#{config[:machine_type]}' not found")
217
277
  exit 1
218
278
  end
219
-
220
- #Fetch server information
221
- cmd_get_instance = "#{@gcompute} getinstance #{server_name} --project_id #{project_id} --print_json "
222
- Chef::Log.debug 'Executing ' + cmd_get_instance
223
- get_instance = exec_shell_cmd(cmd_get_instance)
224
-
225
- if not get_instance.stderr.downcase.scan("error").empty?
226
- ui.error("Failed to fetch server details.")
279
+ begin
280
+ image = client.images.get(:project=>'google', :name=>config[:image]).self_link
281
+ rescue Google::Compute::ResourceNotFound
282
+ ui.error("Image '#{config[:image]}' not found")
283
+ exit 1
284
+ end
285
+ begin
286
+ network = client.networks.get(config[:network]).self_link
287
+ rescue Google::Compute::ResourceNotFound
288
+ ui.error("Network '#{config[:network]}' not found")
227
289
  exit 1
228
290
  end
229
291
 
230
- server = to_json(get_instance.stdout)
231
- private_ip = []
232
- public_ip = []
233
- server["networkInterfaces"].each do |interface|
234
- private_ip << interface["networkIP"]
235
- interface["accessConfigs"].select { |cfg| public_ip << cfg["natIP"] }
292
+ disks = config[:disks].collect{|disk| client.disks.get(:disk=>disk, :zone=>selflink2name(zone)).self_link}
293
+ metadata = config[:metadata].collect{|pair| Hash[*pair.split('=')] }
294
+ network_interface = {'network'=>network}
295
+
296
+ if config[:public_ip] == 'EPHEMERAL'
297
+ network_interface.merge!('accessConfigs' =>[{"name"=>"External NAT",
298
+ "type"=> "ONE_TO_ONE_NAT"}])
299
+ elsif config[:public_ip] =~ /\d+\.\d+\.\d+\.\d+/
300
+ network_interface.merge!('accessConfigs' =>[{"name"=>"External NAT",
301
+ "type"=>"ONE_TO_ONE_NAT", "natIP"=>config[:public_ip] }])
302
+ elsif config[:public_ip] == 'NONE'
303
+ # do nothing
304
+ else
305
+ ui.error("Invalid public ip value : #{config[:public_ip]}")
306
+ exit 1
307
+ end
308
+ zone_operation = client.instances.create(:name=>@name_args.first, :zone=>selflink2name(zone),
309
+ :image=> image,
310
+ :machineType =>machine_type,
311
+ :disks=>disks,
312
+ :metadata=>{'items'=> metadata },
313
+ :networkInterfaces => [network_interface],
314
+ :tags=> config[:tags]
315
+ )
316
+
317
+ ui.info("Waiting for the create server operation to complete")
318
+ until zone_operation.progress.to_i == 100
319
+ ui.info(".")
320
+ sleep 1
321
+ zone_operation = client.zoneOperations.get(:name=>zone_operation, :operation=>zone_operation.name, :zone=>selflink2name(zone))
322
+ end
323
+ ui.info("Waiting for the servers to be in running state")
324
+
325
+ @instance = client.instances.get(:name=>@name_args.first, :zone=>selflink2name(zone))
326
+ msg_pair("Instance Name", @instance.name)
327
+ msg_pair("MachineType", selflink2name(@instance.machine_type))
328
+ msg_pair("Image", selflink2name(@instance.image))
329
+ msg_pair("Zone", selflink2name(@instance.zone))
330
+ msg_pair("Tags", @instance.tags.has_key?("items") ? @instance.tags["items"].join(",") : "None")
331
+ until @instance.status == "RUNNING"
332
+ sleep 3
333
+ msg_pair("Status", @instance.status.downcase)
334
+ @instance = client.instances.get(:name=>@name_args.first, :zone=>selflink2name(zone))
236
335
  end
237
336
 
238
- puts "#{ui.color("Public IP Address", :cyan)}: #{public_ip[0]}"
239
- puts "#{ui.color("Private IP Address", :cyan)}: #{private_ip[0]}"
240
- puts "\n#{ui.color("Waiting for sshd.", :magenta)}"
241
- puts(".") until tcp_test_ssh(public_ip[0], "22") { sleep @initial_sleep_delay ||= 10; puts("done") }
242
- puts "\nBootstrapping #{h.color(server_name, :bold)}..."
243
- bootstrap_for_node(server_name, public_ip[0]).run
244
- end
337
+ msg_pair("Public IP Address", public_ips(@instance)) unless public_ips(@instance).empty?
338
+ msg_pair("Private IP Address", private_ips(@instance))
339
+ ui.info("\n#{ui.color("Waiting for server", :magenta)}")
245
340
 
246
- def bootstrap_for_node(server_name, public_ip)
247
- bootstrap = Chef::Knife::Bootstrap.new
248
- bootstrap.name_args = [public_ip]
249
- bootstrap.config[:run_list] = locate_config_value(:run_list)
250
- bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) || "root"
251
- bootstrap.config[:identity_file] = locate_config_value(:private_key_file)
252
- bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server_name
253
- bootstrap.config[:distro] = locate_config_value(:distro)
254
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
255
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
256
- bootstrap.config[:template_file] = locate_config_value(:template_file)
257
- bootstrap
341
+ ui.info("\n")
342
+ ui.info(ui.color("Waiting for sshd", :magenta))
343
+ wait_for_sshd(ssh_connect_host)
344
+ bootstrap_for_node(@instance,ssh_connect_host).run
345
+ ui.info("\n")
346
+ ui.info("Complete!!")
258
347
  end
259
348
  end
260
349
  end