lib/chef/knife/openstack_server_create.rb in knife-openstack-0.5.4 vs lib/chef/knife/openstack_server_create.rb in knife-openstack-0.6.0
- old
+ new
@@ -1,8 +1,9 @@
#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Author:: Matt Ray (<matt@opscode.com>)
+# Copyright:: Copyright (c) 2011-2012 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -14,138 +15,123 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/knife'
+require 'chef/knife/openstack_base'
class Chef
class Knife
class OpenstackServerCreate < Knife
+ include Knife::OpenstackBase
+
deps do
- require 'chef/knife/bootstrap'
- Chef::Knife::Bootstrap.load_deps
require 'fog'
- require 'socket'
- require 'net/ssh/multi'
require 'readline'
require 'chef/json_compat'
+ require 'chef/knife/bootstrap'
+ Chef::Knife::Bootstrap.load_deps
end
banner "knife openstack server create (options)"
attr_accessor :initial_sleep_delay
option :flavor,
- :short => "-f FLAVOR",
- :long => "--flavor FLAVOR",
- :description => "The flavor of server (m1.small, m1.medium, etc)",
- :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
+ :short => "-f FLAVOR_ID",
+ :long => "--flavor FLAVOR_ID",
+ :description => "The flavor ID of server (m1.small, m1.medium, etc)",
+ :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
option :image,
- :short => "-I IMAGE",
- :long => "--image IMAGE",
- :description => "The AMI for the server",
- :proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
+ :short => "-I IMAGE_ID",
+ :long => "--image IMAGE_ID",
+ :description => "The image ID for the server",
+ :proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
- option :security_groups,
- :short => "-G X,Y,Z",
- :long => "--groups X,Y,Z",
- :description => "The security groups for this server",
- :default => ["default"],
- :proc => Proc.new { |groups| groups.split(',') }
+ # option :security_groups,
+ # :short => "-G X,Y,Z",
+ # :long => "--groups X,Y,Z",
+ # :description => "The security groups for this server",
+ # :default => ["default"],
+ # :proc => Proc.new { |groups| groups.split(',') }
- option :availability_zone,
- :short => "-Z ZONE",
- :long => "--availability-zone ZONE",
- :description => "The Availability Zone",
- :proc => Proc.new { |key| Chef::Config[:knife][:availability_zone] = key }
-
option :chef_node_name,
- :short => "-N NAME",
- :long => "--node-name NAME",
- :description => "The Chef node name for your new node"
+ :short => "-N NAME",
+ :long => "--node-name NAME",
+ :description => "The Chef node name for your new node"
+ option :floating_ip,
+ :short => "-a",
+ :long => "--floating-ip",
+ :boolean => true,
+ :default => false,
+ :description => "Request to associate a floating IP address to the new OpenStack node. Assumes IPs have been allocated to the project."
+
+ option :private_network,
+ :long => "--private-network",
+ :description => "Use the private IP for bootstrapping rather than the public IP",
+ :boolean => true,
+ :default => false
+
option :ssh_key_name,
- :short => "-S KEY",
- :long => "--ssh-key KEY",
- :description => "The OpenStack SSH key id",
- :proc => Proc.new { |key| Chef::Config[:knife][:openstack_ssh_key_id] = key }
+ :short => "-S KEY",
+ :long => "--ssh-key KEY",
+ :description => "The OpenStack SSH keypair id",
+ :proc => Proc.new { |key| Chef::Config[:knife][:openstack_ssh_key_id] = key }
option :ssh_user,
- :short => "-x USERNAME",
- :long => "--ssh-user USERNAME",
- :description => "The ssh username",
- :default => "root"
+ :short => "-x USERNAME",
+ :long => "--ssh-user USERNAME",
+ :description => "The ssh username",
+ :default => "root"
option :ssh_password,
- :short => "-P PASSWORD",
- :long => "--ssh-password PASSWORD",
- :description => "The ssh password"
+ :short => "-P PASSWORD",
+ :long => "--ssh-password PASSWORD",
+ :description => "The ssh password"
option :identity_file,
- :short => "-i IDENTITY_FILE",
- :long => "--identity-file IDENTITY_FILE",
- :description => "The SSH identity file used for authentication"
+ :short => "-i IDENTITY_FILE",
+ :long => "--identity-file IDENTITY_FILE",
+ :description => "The SSH identity file used for authentication"
- option :openstack_access_key_id,
- :short => "-A ID",
- :long => "--openstack-access-key-id KEY",
- :description => "Your OpenStack Access Key ID",
- :proc => Proc.new { |key| Chef::Config[:knife][:openstack_access_key_id] = key }
-
- option :openstack_secret_access_key,
- :short => "-K SECRET",
- :long => "--openstack-secret-access-key SECRET",
- :description => "Your OpenStack API Secret Access Key",
- :proc => Proc.new { |key| Chef::Config[:knife][:openstack_secret_access_key] = key }
-
- option :openstack_api_endpoint,
- :long => "--openstack-api-endpoint ENDPOINT",
- :description => "Your OpenStack API endpoint",
- :proc => Proc.new { |endpoint| Chef::Config[:knife][:openstack_api_endpoint] = endpoint }
-
option :prerelease,
- :long => "--prerelease",
- :description => "Install the pre-release chef gems"
+ :long => "--prerelease",
+ :description => "Install the pre-release chef gems"
option :bootstrap_version,
- :long => "--bootstrap-version VERSION",
- :description => "The version of Chef to install",
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
+ :long => "--bootstrap-version VERSION",
+ :description => "The version of Chef to install",
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
- option :region,
- :long => "--region REGION",
- :description => "Your OpenStack region",
- :proc => Proc.new { |region| Chef::Config[:knife][:region] = region }
-
option :distro,
- :short => "-d DISTRO",
- :long => "--distro DISTRO",
- :description => "Bootstrap a distro using a template",
- :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
- :default => "ubuntu10.04-gems"
+ :short => "-d DISTRO",
+ :long => "--distro DISTRO",
+ :description => "Bootstrap a distro using a template; default is 'chef-full'",
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
+ :default => "chef-full"
option :template_file,
- :long => "--template-file TEMPLATE",
- :description => "Full path to location of template to use",
- :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
- :default => false
+ :long => "--template-file TEMPLATE",
+ :description => "Full path to location of template to use",
+ :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
+ :default => false
option :run_list,
- :short => "-r RUN_LIST",
- :long => "--run-list RUN_LIST",
- :description => "Comma separated list of roles/recipes to apply",
- :proc => lambda { |o| o.split(/[\s,]+/) },
- :default => []
+ :short => "-r RUN_LIST",
+ :long => "--run-list RUN_LIST",
+ :description => "Comma separated list of roles/recipes to apply",
+ :proc => lambda { |o| o.split(/[\s,]+/) },
+ :default => []
- option :no_host_key_verify,
- :long => "--no-host-key-verify",
- :description => "Disable host key verification",
- :boolean => true,
- :default => false
+ option :host_key_verify,
+ :long => "--[no-]host-key-verify",
+ :description => "Verify host key, enabled by default",
+ :boolean => true,
+ :default => true
def tcp_test_ssh(hostname)
tcp_socket = TCPSocket.new(hostname, 22)
readable = IO.select([tcp_socket], nil, nil, 5)
if readable
@@ -155,105 +141,165 @@
else
false
end
rescue Errno::ETIMEDOUT
false
+ rescue Errno::EPERM
+ false
rescue Errno::ECONNREFUSED
sleep 2
false
+ rescue Errno::EHOSTUNREACH
+ sleep 2
+ false
+ rescue Errno::ENETUNREACH
+ sleep 2
+ false
ensure
tcp_socket && tcp_socket.close
end
def run
-
$stdout.sync = true
+ validate!
+
connection = Fog::Compute.new(
- :provider => 'AWS',
- :aws_access_key_id => Chef::Config[:knife][:openstack_access_key_id],
- :aws_secret_access_key => Chef::Config[:knife][:openstack_secret_access_key],
- :endpoint => Chef::Config[:knife][:openstack_api_endpoint],
- :region => locate_config_value(:region)
- )
+ :provider => 'OpenStack',
+ :openstack_username => Chef::Config[:knife][:openstack_username],
+ :openstack_api_key => Chef::Config[:knife][:openstack_password],
+ :openstack_auth_url => Chef::Config[:knife][:openstack_auth_url],
+ :openstack_tenant => Chef::Config[:knife][:openstack_tenant]
+ )
+ #servers require a name, generate one if not passed
+ node_name = get_node_name(config[:chef_node_name])
+
server_def = {
- :image_id => locate_config_value(:image),
- :groups => config[:security_groups],
- :flavor_id => locate_config_value(:flavor),
- :key_name => Chef::Config[:knife][:openstack_ssh_key_id],
- :availability_zone => Chef::Config[:knife][:availability_zone]
- }
+ :name => node_name,
+ :image_ref => locate_config_value(:image),
+ :flavor_ref => locate_config_value(:flavor),
+ # :security_group => locate_config_value(:security_groups),
+ :key_name => Chef::Config[:knife][:openstack_ssh_key_id],
+ :personality => [{
+ "path" => "/etc/chef/ohai/hints/openstack.json",
+ "contents" => ''
+ }]
+ }
- server = connection.servers.create(server_def)
+ Chef::Log.debug("Name #{node_name}")
+ Chef::Log.debug("Image #{locate_config_value(:image)}")
+ Chef::Log.debug("Flavor #{locate_config_value(:flavor)}")
+ # Chef::Log.debug("Groups #{locate_config_value(:security_groups)}")
+ Chef::Log.debug("Creating server #{server_def}")
+ server = connection.servers.create(server_def)
- puts "#{ui.color("Instance ID", :cyan)}: #{server.id}"
- puts "#{ui.color("Flavor", :cyan)}: #{server.flavor_id}"
- puts "#{ui.color("Image", :cyan)}: #{server.image_id}"
- puts "#{ui.color("Availability Zone", :cyan)}: #{server.availability_zone}"
- puts "#{ui.color("Security Groups", :cyan)}: #{server.groups.join(", ")}"
- puts "#{ui.color("SSH Key", :cyan)}: #{server.key_name}"
+ msg_pair("Instance Name", server.name)
+ msg_pair("Instance ID", server.id)
+ # msg_pair("Security Groups", server.groups.join(", "))
+ msg_pair("SSH Keypair", server.key_name)
- print "\n#{ui.color("Waiting for server", :magenta)}"
+ print "\n#{ui.color("Waiting for server", :magenta)}"
- display_name = server.dns_name
+ # wait for it to be ready to do stuff
+ server.wait_for { print "."; ready? }
- # wait for it to be ready to do stuff
- server.wait_for { print "."; ready? }
+ puts("\n")
- puts("\n")
+ msg_pair("Flavor", server.flavor['id'])
+ msg_pair("Image", server.image['id'])
+ msg_pair("Public IP Address", server.public_ip_address['addr']) if server.public_ip_address
- puts "#{ui.color("Public DNS Name", :cyan)}: #{server.dns_name}"
- puts "#{ui.color("Public IP Address", :cyan)}: #{server.public_ip_address}"
- puts "#{ui.color("Private DNS Name", :cyan)}: #{server.private_dns_name}"
- puts "#{ui.color("Private IP Address", :cyan)}: #{server.private_ip_address}"
+ if config[:floating_ip]
+ associated = false
+ connection.addresses.each do |address|
+ if address.instance_id.nil?
+ server.associate_address(address.ip)
+ #a bit of a hack, but server.reload takes a long time
+ server.addresses['public'].push({"version"=>4,"addr"=>address.ip})
+ associated = true
+ msg_pair("Floating IP Address", address.ip)
+ break
+ end
+ end
+ unless associated
+ ui.error("Unable to associate floating IP.")
+ exit 1
+ end
+ end
+ Chef::Log.debug("Public IP Address actual #{server.public_ip_address['addr']}") if server.public_ip_address
- print "\n#{ui.color("Waiting for sshd", :magenta)}"
+ msg_pair("Private IP Address", server.private_ip_address['addr'])
- print(".") until tcp_test_ssh(display_name) {
- sleep @initial_sleep_delay ||= 10
- puts("done")
- }
+ #which IP address to bootstrap
+ bootstrap_ip_address = server.public_ip_address['addr'] if server.public_ip_address
+ if config[:private_network]
+ bootstrap_ip_address = server.private_ip_address['addr']
+ end
+ Chef::Log.debug("Bootstrap IP Address #{bootstrap_ip_address}")
+ if bootstrap_ip_address.nil?
+ ui.error("No IP address available for bootstrapping.")
+ exit 1
+ end
- bootstrap_for_node(server).run
+ print "\n#{ui.color("Waiting for sshd", :magenta)}"
- puts "\n"
- puts "#{ui.color("Instance ID", :cyan)}: #{server.id}"
- puts "#{ui.color("Flavor", :cyan)}: #{server.flavor_id}"
- puts "#{ui.color("Image", :cyan)}: #{server.image_id}"
- puts "#{ui.color("Availability Zone", :cyan)}: #{server.availability_zone}"
- puts "#{ui.color("Security Groups", :cyan)}: #{server.groups.join(", ")}"
- puts "#{ui.color("Public DNS Name", :cyan)}: #{server.dns_name}"
- puts "#{ui.color("Public IP Address", :cyan)}: #{server.public_ip_address}"
- puts "#{ui.color("Private DNS Name", :cyan)}: #{server.private_dns_name}"
- puts "#{ui.color("SSH Key", :cyan)}: #{server.key_name}"
- puts "#{ui.color("Private IP Address", :cyan)}: #{server.private_ip_address}"
- puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
- puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
- end
+ print(".") until tcp_test_ssh(bootstrap_ip_address) {
+ sleep @initial_sleep_delay ||= 10
+ puts("done")
+ }
- def bootstrap_for_node(server)
- bootstrap = Chef::Knife::Bootstrap.new
- bootstrap.name_args = [server.dns_name]
- bootstrap.config[:run_list] = config[:run_list]
- bootstrap.config[:ssh_user] = config[:ssh_user]
- bootstrap.config[:identity_file] = config[:identity_file]
- bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.id
- bootstrap.config[:prerelease] = config[:prerelease]
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
- bootstrap.config[:distro] = locate_config_value(:distro)
- bootstrap.config[:use_sudo] = true
- bootstrap.config[:template_file] = locate_config_value(:template_file)
- bootstrap.config[:environment] = config[:environment]
- # may be needed for vpc_mode
- bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
- bootstrap
- end
+ bootstrap_for_node(server, bootstrap_ip_address).run
- def locate_config_value(key)
- key = key.to_sym
- Chef::Config[:knife][key] || config[key]
+ puts "\n"
+ msg_pair("Instance Name", server.name)
+ msg_pair("Instance ID", server.id)
+ msg_pair("Flavor", server.flavor['id'])
+ msg_pair("Image", server.image['id'])
+ # msg_pair("Security Groups", server.groups.join(", "))
+ msg_pair("SSH Keypair", server.key_name)
+ msg_pair("Public IP Address", server.public_ip_address['addr']) if server.public_ip_address
+ msg_pair("Private IP Address", server.private_ip_address['addr'])
+ msg_pair("Environment", config[:environment] || '_default')
+ msg_pair("Run List", config[:run_list].join(', '))
+ end
+
+ def bootstrap_for_node(server, bootstrap_ip_address)
+ bootstrap = Chef::Knife::Bootstrap.new
+ bootstrap.name_args = [bootstrap_ip_address]
+ bootstrap.config[:run_list] = config[:run_list]
+ bootstrap.config[:ssh_user] = config[:ssh_user]
+ bootstrap.config[:identity_file] = config[:identity_file]
+ bootstrap.config[:host_key_verify] = config[:host_key_verify]
+ bootstrap.config[:chef_node_name] = server.name
+ bootstrap.config[:prerelease] = config[:prerelease]
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
+ bootstrap.config[:distro] = locate_config_value(:distro)
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
+ bootstrap.config[:template_file] = locate_config_value(:template_file)
+ bootstrap.config[:environment] = config[:environment]
+ bootstrap
+ end
+
+ def ami
+ @ami ||= connection.images.get(locate_config_value(:image))
+ end
+
+ def validate!
+
+ super([:image, :openstack_ssh_key_id, :openstack_username, :openstack_password, :openstack_auth_url])
+
+ if ami.nil?
+ ui.error("You have not provided a valid image ID. Please note the short option for this value recently changed from '-i' to '-I'.")
+ exit 1
end
+ end
+ #generate a random name if chef_node_name is empty
+ def get_node_name(chef_node_name)
+ return chef_node_name unless chef_node_name.nil?
+ #lazy uuids
+ chef_node_name = "os-"+rand.to_s.split('.')[1]
end
end
+end
end