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.
- data/.gitignore +22 -0
- data/CONTRIB.md +64 -0
- data/Gemfile +11 -0
- data/README.md +287 -0
- data/Rakefile +53 -0
- data/knife-google.gemspec +26 -0
- data/lib/chef/knife/google_base.rb +39 -68
- data/lib/chef/knife/google_disk_create.rb +60 -0
- data/lib/chef/knife/google_disk_delete.rb +60 -0
- data/lib/chef/knife/google_disk_list.rb +75 -0
- data/lib/chef/knife/google_server_create.rb +273 -184
- data/lib/chef/knife/google_server_delete.rb +74 -32
- data/lib/chef/knife/google_server_list.rb +45 -64
- data/lib/chef/knife/google_setup.rb +31 -0
- data/lib/chef/knife/google_zone_list.rb +78 -0
- data/lib/google/compute.rb +46 -0
- data/lib/google/compute/client.rb +188 -0
- data/lib/google/compute/config.rb +23 -0
- data/lib/google/compute/creatable_resource_collection.rb +38 -0
- data/lib/google/compute/deletable_resource_collection.rb +51 -0
- data/lib/google/compute/disk.rb +40 -0
- data/lib/google/compute/exception.rb +28 -0
- data/lib/google/compute/firewall.rb +65 -0
- data/lib/google/compute/global_operation.rb +60 -0
- data/lib/google/compute/image.rb +30 -0
- data/lib/google/compute/kernel.rb +20 -0
- data/lib/google/compute/listable_resource_collection.rb +33 -0
- data/lib/google/compute/machine_type.rb +36 -0
- data/lib/google/compute/mixins/utils.rb +58 -0
- data/lib/google/compute/network.rb +29 -0
- data/lib/google/compute/project.rb +76 -0
- data/lib/google/compute/resource.rb +81 -0
- data/lib/google/compute/resource_collection.rb +78 -0
- data/lib/google/compute/server.rb +87 -0
- data/lib/google/compute/server/attached_disk.rb +39 -0
- data/lib/google/compute/server/network_interface.rb +38 -0
- data/lib/google/compute/server/network_interface/access_config.rb +35 -0
- data/lib/google/compute/server/serial_port_output.rb +31 -0
- data/lib/google/compute/snapshot.rb +30 -0
- data/lib/google/compute/version.rb +19 -0
- data/lib/google/compute/zone.rb +32 -0
- data/lib/google/compute/zone_operation.rb +60 -0
- data/lib/knife-google/version.rb +18 -2
- data/spec/chef/knife/google_base_spec.rb +46 -0
- data/spec/chef/knife/google_disk_create_spec.rb +36 -0
- data/spec/chef/knife/google_disk_delete_spec.rb +65 -0
- data/spec/chef/knife/google_disk_list_spec.rb +36 -0
- data/spec/chef/knife/google_server_create_spec.rb +84 -0
- data/spec/chef/knife/google_server_delete_spec.rb +105 -0
- data/spec/chef/knife/google_server_list_spec.rb +39 -0
- data/spec/chef/knife/google_setup_spec.rb +25 -0
- data/spec/chef/knife/google_zone_list_spec.rb +32 -0
- data/spec/data/client.json +14 -0
- data/spec/data/compute-v1beta14.json +3386 -0
- data/spec/data/disk.json +15 -0
- data/spec/data/firewall.json +13 -0
- data/spec/data/global_operation.json +36 -0
- data/spec/data/image.json +12 -0
- data/spec/data/kernel.json +15 -0
- data/spec/data/machine_type.json +24 -0
- data/spec/data/network.json +10 -0
- data/spec/data/project.json +21 -0
- data/spec/data/serial_port_output.json +5 -0
- data/spec/data/server.json +46 -0
- data/spec/data/snapshot.json +12 -0
- data/spec/data/zone.json +30 -0
- data/spec/data/zone_operation.json +36 -0
- data/spec/google/compute/disk_spec.rb +105 -0
- data/spec/google/compute/firewall_spec.rb +128 -0
- data/spec/google/compute/global_operation_spec.rb +62 -0
- data/spec/google/compute/image_spec.rb +75 -0
- data/spec/google/compute/kernel_spec.rb +49 -0
- data/spec/google/compute/machine_type_spec.rb +53 -0
- data/spec/google/compute/network_spec.rb +68 -0
- data/spec/google/compute/project_spec.rb +71 -0
- data/spec/google/compute/server_spec.rb +125 -0
- data/spec/google/compute/snapshot_spec.rb +69 -0
- data/spec/google/compute/zone_operation_spec.rb +62 -0
- data/spec/google/compute/zone_spec.rb +50 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/mocks.rb +62 -0
- data/spec/support/resource_examples.rb +70 -0
- data/spec/support/spec_google_base.rb +56 -0
- metadata +121 -31
- 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 '
|
20
|
-
require '
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
36
|
-
|
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
|
40
|
-
|
41
|
-
|
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
|
58
|
-
|
59
|
-
|
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
|
64
|
-
|
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
|
69
|
-
|
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
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
#
|
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 '
|
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
|
-
|
30
|
+
banner "knife google server create NAME -m MACHINE_TYPE -I IMAGE -Z ZONE (options)"
|
37
31
|
|
38
|
-
|
32
|
+
attr_accessor :initial_sleep_delay
|
33
|
+
attr_reader :instance
|
39
34
|
|
40
|
-
option :
|
41
|
-
:short => "-
|
42
|
-
:long => "--
|
43
|
-
:description => "
|
44
|
-
:
|
45
|
-
|
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 :
|
47
|
+
option :zone,
|
48
48
|
:short => "-Z ZONE",
|
49
|
-
:long => "--
|
50
|
-
:description => "The
|
51
|
-
:
|
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 :
|
55
|
-
:short => "-
|
56
|
-
:long => "--
|
57
|
-
:description => "
|
58
|
-
:
|
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 :
|
62
|
-
:
|
63
|
-
:
|
64
|
-
:
|
65
|
-
:
|
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 '
|
77
|
-
:default => "
|
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 :
|
80
|
-
:short => "-
|
81
|
-
:long => "--
|
82
|
-
:description => "
|
83
|
-
:
|
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 :
|
86
|
-
:
|
87
|
-
:
|
88
|
-
:
|
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 :
|
93
|
-
:short => "-
|
94
|
-
:long => "--
|
95
|
-
:description => "
|
96
|
-
:proc =>
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
:
|
101
|
-
:
|
102
|
-
:
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
:
|
107
|
-
:
|
108
|
-
:
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
:short => "-
|
113
|
-
:
|
114
|
-
|
115
|
-
|
116
|
-
:
|
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 :
|
119
|
-
:
|
120
|
-
:
|
121
|
-
:
|
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,
|
147
|
-
tcp_socket = TCPSocket.new(hostname,
|
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::
|
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::
|
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
|
171
|
-
|
172
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
239
|
-
|
240
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|