knife-voxel 0.0.5
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 +6 -0
- data/Gemfile +4 -0
- data/README +37 -0
- data/Rakefile +1 -0
- data/knife-voxel.gemspec +22 -0
- data/lib/chef/knife/voxel_base.rb +40 -0
- data/lib/chef/knife/voxel_devices_list.rb +56 -0
- data/lib/chef/knife/voxel_images_list.rb +25 -0
- data/lib/chef/knife/voxel_voxcloud_create.rb +191 -0
- data/lib/chef/knife/voxel_voxcloud_delete.rb +50 -0
- data/lib/chef/knife/voxel_voxservers_create.rb +191 -0
- data/lib/chef/knife/voxel_voxservers_delete.rb +49 -0
- data/lib/chef/knife/voxel_voxservers_inventory_list.rb +53 -0
- data/lib/knife-voxel/version.rb +5 -0
- metadata +70 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
= Introduction =
|
2
|
+
|
3
|
+
knife-voxel is a plugin for the tool 'knife', part of the configuration
|
4
|
+
management system 'chef'. http://www.opscode.com/. It allows you to
|
5
|
+
provision Voxel dot Net (www.voxel.net) cloud instances (physical as
|
6
|
+
well as virtual) and then bootstrap them as chef clients.
|
7
|
+
|
8
|
+
This plugin is currently a work in progress, although all major
|
9
|
+
functions should work as expected. The command set models Voxel's API
|
10
|
+
hAPI (http://api.voxel.net/docs/) . We depend on the voxel-hapi
|
11
|
+
library for access to this API. This will be replaced with a fog provider
|
12
|
+
based backend in the future.
|
13
|
+
|
14
|
+
|
15
|
+
= Usage =
|
16
|
+
|
17
|
+
** VOXEL COMMANDS **
|
18
|
+
knife voxel voxcloud delete DEVICE_ID (options)
|
19
|
+
knife voxel voxservers inventory list (options)
|
20
|
+
knife voxel images list (options)
|
21
|
+
knife voxel voxcloud create (options)
|
22
|
+
knife voxel devices list (options)
|
23
|
+
knife voxel voxservers delete DEVICE_ID (options)
|
24
|
+
knife voxel voxservers create (options)
|
25
|
+
|
26
|
+
|
27
|
+
= Examples =
|
28
|
+
|
29
|
+
> knife voxel voxcloud delete 12345
|
30
|
+
|
31
|
+
> knife voxel voxcloud create --image-id 55 --hostname app1.domain.com \
|
32
|
+
--processing-cores 4 --disk-size 20 --facility LGA8
|
33
|
+
|
34
|
+
= Author =
|
35
|
+
|
36
|
+
J. W. Brinkerhoff <jwb@voxel.net>
|
37
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/knife-voxel.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "knife-voxel/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "knife-voxel"
|
7
|
+
s.version = Knife::Voxel::VERSION
|
8
|
+
s.authors = ["James W. Brinkerhoff"]
|
9
|
+
s.email = ["jwb@voxel.net"]
|
10
|
+
s.homepage = "http://api.voxel.net/"
|
11
|
+
s.summary = "Voxel hAPI Support for Chef's knife command"
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
# specify any dependencies here; for example:
|
20
|
+
# s.add_development_dependency "rspec"
|
21
|
+
s.add_runtime_dependency "voxel-hapi", ">= 1.1.10"
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
module VoxelBase
|
6
|
+
def self.included(includer)
|
7
|
+
includer.class_eval do
|
8
|
+
|
9
|
+
deps do
|
10
|
+
require 'hapi'
|
11
|
+
end
|
12
|
+
|
13
|
+
option :voxel_api_key,
|
14
|
+
:short => "-K KEY",
|
15
|
+
:long => "--voxel-api-key KEY",
|
16
|
+
:description => "Voxel hAPI Key",
|
17
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:voxel_api_key] = key }
|
18
|
+
|
19
|
+
option :voxel_api_secret,
|
20
|
+
:short => "-S SECRET",
|
21
|
+
:long => "--voxel-api-secret SECRET",
|
22
|
+
:description => "Voxel hAPI Secret",
|
23
|
+
:proc => Proc.new { |secret| Chef::Config[:knife][:voxel_api_secret] = secret }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def hapi
|
28
|
+
@hapi ||= begin
|
29
|
+
hapi = HAPI.new(
|
30
|
+
:useauthkey => true,
|
31
|
+
:username => Chef::Config[:knife][:voxel_api_key],
|
32
|
+
:password => Chef::Config[:knife][:voxel_api_secret],
|
33
|
+
:default_format => :ruby,
|
34
|
+
:debug => false
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'chef/knife/voxel_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class VoxelDevicesList < Knife
|
6
|
+
include Knife::VoxelBase
|
7
|
+
|
8
|
+
banner "knife voxel devices list (options)"
|
9
|
+
|
10
|
+
def devices_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
devices = [ ui.color('ID', :bold), ui.color('Name', :bold), ui.color('Type', :bold), ui.color('Status', :bold), ui.color('IP', :bold) ]
|
15
|
+
statuses = hapi.helper_devices_status
|
16
|
+
|
17
|
+
devices_list = hapi.voxel_devices_list['devices']
|
18
|
+
|
19
|
+
unless devices_list.empty?
|
20
|
+
devices_list['device'] = [ devices_list['device'] ] if devices_list['device'].is_a?(Hash)
|
21
|
+
|
22
|
+
devices_list['device'].each do |device|
|
23
|
+
devices << device['id']
|
24
|
+
devices << device['label']
|
25
|
+
devices << case device['type']['content']
|
26
|
+
when "Virtual Server"
|
27
|
+
"VoxCLOUD"
|
28
|
+
when "Server"
|
29
|
+
"VoxSERVER"
|
30
|
+
else
|
31
|
+
device['type']['content']
|
32
|
+
end
|
33
|
+
|
34
|
+
devices << (statuses.has_key?(device['id']) ? statuses[device['id']] : "N/A")
|
35
|
+
|
36
|
+
if device.has_key?('ipassignments')
|
37
|
+
ips = device['ipassignments']['ipassignment']
|
38
|
+
|
39
|
+
if ips.is_a?(Hash)
|
40
|
+
ips = [ ips ]
|
41
|
+
end
|
42
|
+
|
43
|
+
ip = ips.select { |a| a['type'] == "frontend" }.first
|
44
|
+
|
45
|
+
devices << (ip.nil? ? "" : ip["content"])
|
46
|
+
else
|
47
|
+
devices << ""
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
puts ui.list(devices, :columns_across, 5)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'chef/knife/voxel_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class VoxelImagesList < Knife
|
6
|
+
include Knife::VoxelBase
|
7
|
+
|
8
|
+
banner "knife voxel images list (options)"
|
9
|
+
|
10
|
+
def images_list
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
images = [ ui.color('ID', :bold), ui.color('Name', :bold) ]
|
15
|
+
|
16
|
+
hapi.voxel_images_list['images']['image'].each do |image|
|
17
|
+
images << image['id']
|
18
|
+
images << image['summary']
|
19
|
+
end
|
20
|
+
|
21
|
+
puts ui.list(images, :columns_across, 2)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'chef/knife/voxel_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class VoxelVoxcloudCreate < Knife
|
6
|
+
include Knife::VoxelBase
|
7
|
+
|
8
|
+
deps do
|
9
|
+
require 'chef/json_compat'
|
10
|
+
require 'chef/knife/bootstrap'
|
11
|
+
require 'hapi'
|
12
|
+
require 'readline'
|
13
|
+
|
14
|
+
Chef::Knife::Bootstrap.load_deps
|
15
|
+
end
|
16
|
+
|
17
|
+
banner "knife voxel voxcloud create (options)"
|
18
|
+
|
19
|
+
option :processing_cores,
|
20
|
+
:long => "--processing-cores CORES",
|
21
|
+
:description => "Total number of processing cores. Memory is 2048xCORES",
|
22
|
+
:default => 1
|
23
|
+
|
24
|
+
option :disk_size,
|
25
|
+
:long => "--disk-size SIZE",
|
26
|
+
:description => "Disk Volume Size, in GB",
|
27
|
+
:default => 10
|
28
|
+
|
29
|
+
option :image_id,
|
30
|
+
:long => "--image-id IMAGE",
|
31
|
+
:description => "Image Id to Install",
|
32
|
+
:required => true
|
33
|
+
|
34
|
+
option :hostname,
|
35
|
+
:long => "--hostname NAME",
|
36
|
+
:description => "The server's hostname",
|
37
|
+
:required => true
|
38
|
+
|
39
|
+
option :chef_node_name,
|
40
|
+
:short => "-N NAME",
|
41
|
+
:long => "--node-name NAME",
|
42
|
+
:description => "The Chef node name for your new node"
|
43
|
+
|
44
|
+
option :ssh_user,
|
45
|
+
:short => "-x USERNAME",
|
46
|
+
:long => "--ssh-user USERNAME",
|
47
|
+
:description => "The ssh username; default is 'root'",
|
48
|
+
:default => "root"
|
49
|
+
|
50
|
+
option :ssh_password,
|
51
|
+
:short => "-P PASSWORD",
|
52
|
+
:long => "--ssh-password PASSWORD",
|
53
|
+
:description => "The ssh password"
|
54
|
+
|
55
|
+
option :facility,
|
56
|
+
:long => "--facility FACILITY",
|
57
|
+
:description => "Voxel Facility (LDJ1, LGA8, AMS2, SIN1, etc)",
|
58
|
+
:required => true
|
59
|
+
|
60
|
+
option :identity_file,
|
61
|
+
:short => "-i IDENTITY_FILE",
|
62
|
+
:long => "--identity-file IDENTITY_FILE",
|
63
|
+
:description => "The SSH identity file used for authentication"
|
64
|
+
|
65
|
+
option :prerelease,
|
66
|
+
:long => "--prerelease",
|
67
|
+
:description => "Install the pre-release chef gems"
|
68
|
+
|
69
|
+
option :bootstrap_version,
|
70
|
+
:long => "--bootstrap-version VERSION",
|
71
|
+
:description => "The version of Chef to install",
|
72
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
73
|
+
|
74
|
+
option :distro,
|
75
|
+
:short => "-d DISTRO",
|
76
|
+
:long => "--distro DISTRO",
|
77
|
+
:description => "Bootstrap a distro using a template; default is 'ubuntu10.04-gems'",
|
78
|
+
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
|
79
|
+
:default => "ubuntu10.04-gems"
|
80
|
+
|
81
|
+
option :template_file,
|
82
|
+
:long => "--template-file TEMPLATE",
|
83
|
+
:description => "Full path to location of template to use",
|
84
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
85
|
+
:default => false
|
86
|
+
|
87
|
+
option :run_list,
|
88
|
+
:short => "-r RUN_LIST",
|
89
|
+
:long => "--run-list RUN_LIST",
|
90
|
+
:description => "Comma separated list of roles/recipes to apply",
|
91
|
+
:proc => lambda { |o| o.split(/[\s,]+/) },
|
92
|
+
:default => []
|
93
|
+
|
94
|
+
def bootstrap_for_node(device)
|
95
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
96
|
+
|
97
|
+
bootstrap.name_args = [device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']]
|
98
|
+
bootstrap.config[:run_list] = config[:run_list]
|
99
|
+
bootstrap.config[:ssh_user] = config[:ssh_user] || "root"
|
100
|
+
bootstrap.config[:ssh_password] = device['accessmethods']['accessmethod'].select { |a| a['type'] == 'admin' }.first['password']
|
101
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
102
|
+
bootstrap.config[:chef_node_name] = config[:chef_node_name] || "d#{device['id']}"
|
103
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
104
|
+
bootstrap.config[:bootstrap_version] = config[:bootstrap_version]
|
105
|
+
bootstrap.config[:distro] = config[:distro]
|
106
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
107
|
+
bootstrap.config[:template_file] = config[:template_file]
|
108
|
+
bootstrap.config[:environment] = config[:environment]
|
109
|
+
bootstrap
|
110
|
+
end
|
111
|
+
|
112
|
+
def tcp_test_ssh(hostname)
|
113
|
+
begin
|
114
|
+
tcp_socket = TCPSocket.new(hostname, 22)
|
115
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
116
|
+
if readable
|
117
|
+
Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
|
118
|
+
yield
|
119
|
+
true
|
120
|
+
else
|
121
|
+
false
|
122
|
+
end
|
123
|
+
rescue Errno::ETIMEDOUT
|
124
|
+
false
|
125
|
+
rescue Errno::EPERM
|
126
|
+
false
|
127
|
+
rescue Errno::ECONNREFUSED
|
128
|
+
sleep 2
|
129
|
+
false
|
130
|
+
rescue Errno::EHOSTUNREACH
|
131
|
+
sleep 2
|
132
|
+
false
|
133
|
+
ensure
|
134
|
+
tcp_socket && tcp_socket.close
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def run
|
139
|
+
$stdout.sync = true
|
140
|
+
|
141
|
+
create = hapi.voxel_voxcloud_create(
|
142
|
+
:image_id => config[:image_id],
|
143
|
+
:hostname => config[:hostname],
|
144
|
+
:processing_cores => config[:processing_cores],
|
145
|
+
:facility => config[:facility],
|
146
|
+
:disk_size => config[:disk_size]
|
147
|
+
)
|
148
|
+
|
149
|
+
if create['stat'] == "fail"
|
150
|
+
ui.error(create['err']['msg'])
|
151
|
+
else
|
152
|
+
sleep 2
|
153
|
+
|
154
|
+
device = hapi.voxel_devices_list( :device_id => create['device']['id'], :verbosity => 'extended' )
|
155
|
+
|
156
|
+
if device['stat'] == "fail"
|
157
|
+
ui.error("Device Listing Failed: #{device['err']['msg']}")
|
158
|
+
else
|
159
|
+
device = device['devices']['device']
|
160
|
+
|
161
|
+
puts "#{ui.color("Device ID", :cyan)}: #{device['id']}"
|
162
|
+
puts "#{ui.color("Name", :cyan)}: #{device['label']}"
|
163
|
+
puts "#{ui.color("Image Id", :cyan)}: #{config[:image_id]}"
|
164
|
+
puts "#{ui.color("Facility", :cyan)}: #{device['location']['facility']['code']}"
|
165
|
+
puts "#{ui.color("Public IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']}"
|
166
|
+
puts "#{ui.color("Private IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'backend' }.first['content']}"
|
167
|
+
puts "#{ui.color("Root Password", :cyan)}: #{device['accessmethods']['accessmethod'].select { |a| a['type'] == 'admin' }.first['password']}"
|
168
|
+
|
169
|
+
status = hapi.voxel_voxcloud_status( :device_id => device['id'], :verbosity => 'extended' )
|
170
|
+
|
171
|
+
while %w{ QUEUED IN_PROGRESS }.include?( status['devices']['device']['status'] ) do
|
172
|
+
print "."
|
173
|
+
status = hapi.voxel_voxcloud_status( :device_id => device['id'], :verbosity => 'extended' )
|
174
|
+
sleep 10
|
175
|
+
end
|
176
|
+
|
177
|
+
print "\n#{ui.color("Waiting for sshd", :magenta)}"
|
178
|
+
|
179
|
+
print(".") until tcp_test_ssh(device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']) { sleep @initial_sleep_delay ||= 10; puts("done") }
|
180
|
+
|
181
|
+
bootstrap_for_node(device).run
|
182
|
+
|
183
|
+
puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
|
184
|
+
puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'chef/knife/voxel_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class VoxelVoxcloudDelete < Knife
|
6
|
+
include Knife::VoxelBase
|
7
|
+
|
8
|
+
banner "knife voxel voxcloud delete DEVICE_ID (options)"
|
9
|
+
|
10
|
+
def run
|
11
|
+
if @name_args.empty?
|
12
|
+
ui.error( "knife voxel voxcloud delete DEVICE_ID" )
|
13
|
+
else
|
14
|
+
|
15
|
+
@name_args.each do |device_id|
|
16
|
+
device = hapi.voxel_devices_list( :device_id => device_id, :verbosity => 'extended' )
|
17
|
+
|
18
|
+
if device['stat'] == "fail"
|
19
|
+
ui.error(device['err']['msg'])
|
20
|
+
else
|
21
|
+
device = device['devices']['device']
|
22
|
+
|
23
|
+
if device['type']['content'] == "Virtual Server"
|
24
|
+
puts "#{ui.color("Device ID", :cyan)}: #{device['id']}"
|
25
|
+
puts "#{ui.color("Name", :cyan)}: #{device['label']}"
|
26
|
+
puts "#{ui.color("Image Id", :cyan)}: #{config[:image_id]}"
|
27
|
+
puts "#{ui.color("Facility", :cyan)}: #{device['location']['facility']['code']}"
|
28
|
+
puts "#{ui.color("Public IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']}"
|
29
|
+
puts "#{ui.color("Private IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'backend' }.first['content']}"
|
30
|
+
puts "\n"
|
31
|
+
|
32
|
+
confirm("Do you really want to delete this VoxCLOUD device")
|
33
|
+
|
34
|
+
delete = hapi.voxel_voxcloud_delete( :device_id => device_id )
|
35
|
+
|
36
|
+
if delete['stat'] == "ok"
|
37
|
+
ui.warn("Deleted VoxCLOUD device #{device['id']} named #{device['label']}")
|
38
|
+
else
|
39
|
+
ui.error("Error removing VoxCLOUD device #{device['label']}")
|
40
|
+
end
|
41
|
+
else
|
42
|
+
ui.warn("Device #{device['id']} is not a VoxCLOUD device.")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'chef/knife/voxel_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class VoxelVoxserversCreate < Knife
|
6
|
+
include Knife::VoxelBase
|
7
|
+
|
8
|
+
deps do
|
9
|
+
require 'chef/json_compat'
|
10
|
+
require 'chef/knife/bootstrap'
|
11
|
+
require 'hapi'
|
12
|
+
require 'readline'
|
13
|
+
|
14
|
+
Chef::Knife::Bootstrap.load_deps
|
15
|
+
end
|
16
|
+
|
17
|
+
banner "knife voxel voxservers create (options)"
|
18
|
+
|
19
|
+
option :configuration_id,
|
20
|
+
:long => "--configuration-id CONFIGID",
|
21
|
+
:description => "VoxSERVER Configuration Id",
|
22
|
+
:required => true
|
23
|
+
|
24
|
+
option :swap_size,
|
25
|
+
:long => "--swap-size SIZE",
|
26
|
+
:description => "Swap Partition Size, in GB",
|
27
|
+
:default => 4
|
28
|
+
|
29
|
+
option :image_id,
|
30
|
+
:long => "--image-id IMAGE",
|
31
|
+
:description => "Image Id to Install",
|
32
|
+
:required => true
|
33
|
+
|
34
|
+
option :hostname,
|
35
|
+
:long => "--hostname NAME",
|
36
|
+
:description => "The server's hostname",
|
37
|
+
:required => true
|
38
|
+
|
39
|
+
option :chef_node_name,
|
40
|
+
:short => "-N NAME",
|
41
|
+
:long => "--node-name NAME",
|
42
|
+
:description => "The Chef node name for your new node"
|
43
|
+
|
44
|
+
option :ssh_user,
|
45
|
+
:short => "-x USERNAME",
|
46
|
+
:long => "--ssh-user USERNAME",
|
47
|
+
:description => "The ssh username; default is 'root'",
|
48
|
+
:default => "root"
|
49
|
+
|
50
|
+
option :ssh_password,
|
51
|
+
:short => "-P PASSWORD",
|
52
|
+
:long => "--ssh-password PASSWORD",
|
53
|
+
:description => "The ssh password"
|
54
|
+
|
55
|
+
option :facility,
|
56
|
+
:long => "--facility FACILITY",
|
57
|
+
:description => "Voxel Facility (LDJ1, LGA8, AMS2, SIN1, etc)",
|
58
|
+
:required => true
|
59
|
+
|
60
|
+
option :identity_file,
|
61
|
+
:short => "-i IDENTITY_FILE",
|
62
|
+
:long => "--identity-file IDENTITY_FILE",
|
63
|
+
:description => "The SSH identity file used for authentication"
|
64
|
+
|
65
|
+
option :prerelease,
|
66
|
+
:long => "--prerelease",
|
67
|
+
:description => "Install the pre-release chef gems"
|
68
|
+
|
69
|
+
option :bootstrap_version,
|
70
|
+
:long => "--bootstrap-version VERSION",
|
71
|
+
:description => "The version of Chef to install",
|
72
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
73
|
+
|
74
|
+
option :distro,
|
75
|
+
:short => "-d DISTRO",
|
76
|
+
:long => "--distro DISTRO",
|
77
|
+
:description => "Bootstrap a distro using a template; default is 'ubuntu10.04-gems'",
|
78
|
+
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
|
79
|
+
:default => "ubuntu10.04-gems"
|
80
|
+
|
81
|
+
option :template_file,
|
82
|
+
:long => "--template-file TEMPLATE",
|
83
|
+
:description => "Full path to location of template to use",
|
84
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
85
|
+
:default => false
|
86
|
+
|
87
|
+
option :run_list,
|
88
|
+
:short => "-r RUN_LIST",
|
89
|
+
:long => "--run-list RUN_LIST",
|
90
|
+
:description => "Comma separated list of roles/recipes to apply",
|
91
|
+
:proc => lambda { |o| o.split(/[\s,]+/) },
|
92
|
+
:default => []
|
93
|
+
|
94
|
+
def bootstrap_for_node(device)
|
95
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
96
|
+
|
97
|
+
bootstrap.name_args = [device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']]
|
98
|
+
bootstrap.config[:run_list] = config[:run_list]
|
99
|
+
bootstrap.config[:ssh_user] = config[:ssh_user] || "root"
|
100
|
+
bootstrap.config[:ssh_password] = device['accessmethods']['accessmethod'].select { |a| a['type'] == 'admin' }.first['password']
|
101
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
102
|
+
bootstrap.config[:chef_node_name] = config[:chef_node_name] || "d#{device['id']}"
|
103
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
104
|
+
bootstrap.config[:bootstrap_version] = config[:bootstrap_version]
|
105
|
+
bootstrap.config[:distro] = config[:distro]
|
106
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
107
|
+
bootstrap.config[:template_file] = config[:template_file]
|
108
|
+
bootstrap.config[:environment] = config[:environment]
|
109
|
+
bootstrap
|
110
|
+
end
|
111
|
+
|
112
|
+
def tcp_test_ssh(hostname)
|
113
|
+
begin
|
114
|
+
tcp_socket = TCPSocket.new(hostname, 22)
|
115
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
116
|
+
if readable
|
117
|
+
Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
|
118
|
+
yield
|
119
|
+
true
|
120
|
+
else
|
121
|
+
false
|
122
|
+
end
|
123
|
+
rescue Errno::ETIMEDOUT
|
124
|
+
false
|
125
|
+
rescue Errno::EPERM
|
126
|
+
false
|
127
|
+
rescue Errno::ECONNREFUSED
|
128
|
+
sleep 2
|
129
|
+
false
|
130
|
+
rescue Errno::EHOSTUNREACH
|
131
|
+
sleep 2
|
132
|
+
false
|
133
|
+
ensure
|
134
|
+
tcp_socket && tcp_socket.close
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def run
|
139
|
+
$stdout.sync = true
|
140
|
+
|
141
|
+
create = hapi.voxel_voxservers_create(
|
142
|
+
:image_id => config[:image_id],
|
143
|
+
:hostname => config[:hostname],
|
144
|
+
:configuration_id => config[:configuration_id],
|
145
|
+
:facility => config[:facility],
|
146
|
+
:swap_space => config[:swap_size]
|
147
|
+
)
|
148
|
+
|
149
|
+
if create['stat'] == "fail"
|
150
|
+
ui.error(create['err']['msg'])
|
151
|
+
else
|
152
|
+
sleep 2
|
153
|
+
|
154
|
+
device = hapi.voxel_devices_list( :device_id => create['device']['id'], :verbosity => 'extended' )
|
155
|
+
|
156
|
+
if device['stat'] == "fail"
|
157
|
+
ui.error(device['err']['msg'])
|
158
|
+
else
|
159
|
+
device = device['devices']['device']
|
160
|
+
|
161
|
+
puts "#{ui.color("Device ID", :cyan)}: #{device['id']}"
|
162
|
+
puts "#{ui.color("Name", :cyan)}: #{device['label']}"
|
163
|
+
puts "#{ui.color("Image Id", :cyan)}: #{config[:image_id]}"
|
164
|
+
puts "#{ui.color("Facility", :cyan)}: #{device['location']['facility']['code']}"
|
165
|
+
puts "#{ui.color("Public IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']}"
|
166
|
+
puts "#{ui.color("Private IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'backend' }.first['content']}"
|
167
|
+
puts "#{ui.color("Root Password", :cyan)}: #{device['accessmethods']['accessmethod'].select { |a| a['type'] == 'admin' }.first['password']}"
|
168
|
+
|
169
|
+
status = hapi.voxel_voxservers_status( :device_id => device['id'], :verbosity => 'extended' )
|
170
|
+
|
171
|
+
while %w{ QUEUED IN_PROGRESS }.include?( status['devices']['device']['status'] ) do
|
172
|
+
print "."
|
173
|
+
status = hapi.voxel_voxservers_status( :device_id => device['id'], :verbosity => 'extended' )
|
174
|
+
sleep 10
|
175
|
+
end
|
176
|
+
|
177
|
+
print "\n#{ui.color("Waiting for sshd", :magenta)}"
|
178
|
+
|
179
|
+
print(".") until tcp_test_ssh(device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']) { sleep @initial_sleep_delay ||= 10; puts("done") }
|
180
|
+
|
181
|
+
bootstrap_for_node(device).run
|
182
|
+
|
183
|
+
puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
|
184
|
+
puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'chef/knife/voxel_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class VoxelVoxserversDelete < Knife
|
6
|
+
include Knife::VoxelBase
|
7
|
+
|
8
|
+
banner "knife voxel voxservers delete DEVICE_ID (options)"
|
9
|
+
|
10
|
+
def run
|
11
|
+
if @name_args.empty?
|
12
|
+
ui.error("knife voxel voxservers delete DEVICE_ID (options)")
|
13
|
+
else
|
14
|
+
@name_args.each do |device_id|
|
15
|
+
device = hapi.voxel_devices_list( :device_id => device_id, :verbosity => 'extended' )
|
16
|
+
|
17
|
+
if device['stat'] == "fail"
|
18
|
+
ui.error(device['err']['msg'])
|
19
|
+
else
|
20
|
+
device = device['devices']['device']
|
21
|
+
|
22
|
+
if device['type']['content'] == "Server"
|
23
|
+
puts "#{ui.color("Device ID", :cyan)}: #{device['id']}"
|
24
|
+
puts "#{ui.color("Name", :cyan)}: #{device['label']}"
|
25
|
+
puts "#{ui.color("Image Id", :cyan)}: #{config[:image_id]}"
|
26
|
+
puts "#{ui.color("Facility", :cyan)}: #{device['location']['facility']['code']}"
|
27
|
+
puts "#{ui.color("Public IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'frontend' }.first['content']}"
|
28
|
+
puts "#{ui.color("Private IP Address", :cyan)}: #{device['ipassignments']['ipassignment'].select { |i| i['type'] == 'backend' }.first['content']}"
|
29
|
+
puts "\n"
|
30
|
+
|
31
|
+
confirm("Do you really want to delete this VoxSERVER device")
|
32
|
+
|
33
|
+
delete = hapi.voxel_voxservers_delete( :device_id => device_id )
|
34
|
+
|
35
|
+
if delete['stat'] == "ok"
|
36
|
+
ui.info("Deleted VoxSERVER device #{device['id']} named #{device['label']}")
|
37
|
+
else
|
38
|
+
ui.error("Error removing VoxSERVER device #{device['label']}: #{delete['err']['msg']}")
|
39
|
+
end
|
40
|
+
else
|
41
|
+
ui.error("Device #{device['id']} is not a VoxSERVER device.")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'chef/knife/voxel_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class VoxelVoxserversInventoryList < Knife
|
6
|
+
include Knife::VoxelBase
|
7
|
+
|
8
|
+
banner "knife voxel voxservers inventory list (options)"
|
9
|
+
|
10
|
+
def run
|
11
|
+
inventory_header = [ ui.color('ID', :bold), ui.color('Summary', :bold) ]
|
12
|
+
|
13
|
+
facilities = hapi.voxel_voxservers_facilities_list
|
14
|
+
|
15
|
+
unless facilities['facilities'].empty?
|
16
|
+
facilities['facilities']['facility'].each do |facility|
|
17
|
+
puts ui.color("#{facility['label']} (#{facility['description']})\n", :bold)
|
18
|
+
|
19
|
+
available_inventory = hapi.voxel_voxservers_inventory_list( :facility => facility['label'], :verbosity => 'compact' )
|
20
|
+
|
21
|
+
if available_inventory['facilities'].empty?
|
22
|
+
puts "No inventory available at this time."
|
23
|
+
else
|
24
|
+
inventory = available_inventory['facilities']['facility']['configuration']
|
25
|
+
inventory = [ inventory ] if inventory.is_a?(Hash)
|
26
|
+
|
27
|
+
local_inventory = inventory_header.clone
|
28
|
+
inventory.each do |cfg|
|
29
|
+
local_inventory << cfg['id']
|
30
|
+
local_inventory << cfg['summary']
|
31
|
+
end
|
32
|
+
|
33
|
+
puts ui.list(local_inventory, :columns_across, 2)
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# unless available_inventory['facilities'].empty?
|
42
|
+
# if available_inventory['facilities']['facility'].is_a?(Hash)
|
43
|
+
# available_inventory['facilities']['facility'] = [ available_inventory['facilities']['facility'] ]
|
44
|
+
#
|
45
|
+
# available_inventory['facilities']['facility'].each do |cfg|
|
46
|
+
#
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knife-voxel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James W. Brinkerhoff
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-13 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: voxel-hapi
|
16
|
+
requirement: &14538140 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.10
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *14538140
|
25
|
+
description: Voxel hAPI Support for Chef's knife command
|
26
|
+
email:
|
27
|
+
- jwb@voxel.net
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- README
|
35
|
+
- Rakefile
|
36
|
+
- knife-voxel.gemspec
|
37
|
+
- lib/chef/knife/voxel_base.rb
|
38
|
+
- lib/chef/knife/voxel_devices_list.rb
|
39
|
+
- lib/chef/knife/voxel_images_list.rb
|
40
|
+
- lib/chef/knife/voxel_voxcloud_create.rb
|
41
|
+
- lib/chef/knife/voxel_voxcloud_delete.rb
|
42
|
+
- lib/chef/knife/voxel_voxservers_create.rb
|
43
|
+
- lib/chef/knife/voxel_voxservers_delete.rb
|
44
|
+
- lib/chef/knife/voxel_voxservers_inventory_list.rb
|
45
|
+
- lib/knife-voxel/version.rb
|
46
|
+
homepage: http://api.voxel.net/
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.10
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Voxel hAPI Support for Chef's knife command
|
70
|
+
test_files: []
|