1818
1919require 'chef/knife'
2020require 'chef/json'
21+ require 'uuidtools'
2122
2223class Chef
2324 class Knife
@@ -36,14 +37,29 @@ class RackspaceServerCreate < Knife
3637 :short => "-i IMAGE" ,
3738 :long => "--image IMAGE" ,
3839 :description => "The image of the server" ,
39- :proc => Proc . new { |i | i . to_i } ,
40- :default => 14362
40+ :proc => Proc . new { |i | i . to_i }
4141
4242 option :server_name ,
43- :short => "-N NAME" ,
43+ :short => "-S NAME" ,
4444 :long => "--server-name NAME" ,
45- :description => "The server name" ,
46- :default => "wtf"
45+ :description => "The server name. Defaults to a random UUID." ,
46+ :default => UUIDTools ::UUID . random_create . to_s
47+
48+ option :chef_node_name ,
49+ :short => "-N NAME" ,
50+ :long => "--node-name NAME" ,
51+ :description => "The Chef node name for your new node"
52+
53+ option :ssh_user ,
54+ :short => "-x USERNAME" ,
55+ :long => "--ssh-user USERNAME" ,
56+ :description => "The ssh username" ,
57+ :default => "root"
58+
59+ option :ssh_password ,
60+ :short => "-P PASSWORD" ,
61+ :long => "--ssh-password PASSWORD" ,
62+ :description => "The ssh password"
4763
4864 option :api_key ,
4965 :short => "-K KEY" ,
@@ -57,97 +73,109 @@ class RackspaceServerCreate < Knife
5773 :description => "Your rackspace API username" ,
5874 :proc => Proc . new { |username | Chef ::Config [ :knife ] [ :rackspace_api_username ] = username }
5975
76+ option :prerelease ,
77+ :long => "--prerelease" ,
78+ :description => "Install the pre-release chef gems"
79+
80+ option :distro ,
81+ :short => "-d DISTRO" ,
82+ :long => "--distro DISTRO" ,
83+ :description => "Bootstrap a distro using a template" ,
84+ :default => "ubuntu10.04-gems"
85+
86+ option :template_file ,
87+ :long => "--template-file TEMPLATE" ,
88+ :description => "Full path to location of template to use" ,
89+ :default => false
90+
6091 def h
6192 @highline ||= HighLine . new
6293 end
6394
95+ def tcp_test_ssh ( hostname )
96+ tcp_socket = TCPSocket . new ( hostname , 22 )
97+ readable = IO . select ( [ tcp_socket ] , nil , nil , 5 )
98+ if readable
99+ Chef ::Log . debug ( "sshd accepting connections on #{ hostname } , banner is #{ tcp_socket . gets } " )
100+ yield
101+ true
102+ else
103+ false
104+ end
105+ rescue Errno ::ETIMEDOUT
106+ false
107+ rescue Errno ::ECONNREFUSED
108+ sleep 2
109+ false
110+ ensure
111+ tcp_socket && tcp_socket . close
112+ end
113+
64114 def run
65115 require 'fog'
66116 require 'highline'
67117 require 'net/ssh/multi'
68118 require 'readline'
69119
120+ $stdout. sync = true
121+
70122 connection = Fog ::Rackspace ::Servers . new (
71123 :rackspace_api_key => Chef ::Config [ :knife ] [ :rackspace_api_key ] ,
72124 :rackspace_username => Chef ::Config [ :knife ] [ :rackspace_api_username ]
73125 )
74126
75- server = connection . servers . new
76-
77- server . flavor_id = config [ :flavor ]
78- server . image_id = config [ :image ]
79- server . name = config [ :server_name ]
80- server . personality = [
81- {
82- 'path' => '/etc/install-chef' ,
83- 'contents' => <<-EOH
84- #!/bin/bash
85- # Customized rc.local for chef installation
86-
87- if [ ! -f /usr/bin/chef-client ]; then
88- apt-get update
89- apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras
90- cd /tmp
91- wget http://rubyforge.org/frs/download.php/69365/rubygems-1.3.6.tgz
92- tar xvf rubygems-1.3.6.tgz
93- cd rubygems-1.3.6
94- ruby setup.rb
95- cp /usr/bin/gem1.8 /usr/bin/gem
96- gem install chef ohai --no-rdoc --no-ri --verbose
97- fi
98-
99- exit 0
100- EOH
101- } ,
102- {
103- 'path' => "/etc/chef/validation.pem" ,
104- 'contents' => IO . read ( Chef ::Config [ :validation_key ] )
105- } ,
106- {
107- 'path' => "/etc/chef/client.rb" ,
108- 'contents' => <<-EOH
109- log_level :info
110- log_location STDOUT
111- chef_server_url "#{ Chef ::Config [ :chef_server_url ] } "
112- validation_client_name "#{ Chef ::Config [ :validation_client_name ] } "
113- EOH
114- } ,
115- {
116- 'path' => "/etc/chef/first-boot.json" ,
117- 'contents' => { "run_list" => @name_args } . to_json
118- } ,
119- ]
120-
121- server . save
122-
123- $stdout. sync = true
127+ server = connection . servers . create (
128+ :name => config [ :server_name ] ,
129+ :image_id => config [ :image ] ,
130+ :flavor_id => config [ :flavor ]
131+ )
124132
133+ puts "#{ h . color ( "Instance ID" , :cyan ) } : #{ server . id } "
125134 puts "#{ h . color ( "Name" , :cyan ) } : #{ server . name } "
126135 puts "#{ h . color ( "Flavor" , :cyan ) } : #{ server . flavor_id } "
127136 puts "#{ h . color ( "Image" , :cyan ) } : #{ server . image_id } "
128- puts "#{ h . color ( "Public Address" , :cyan ) } : #{ server . addresses [ "public" ] } "
129- puts "#{ h . color ( "Private Address" , :cyan ) } : #{ server . addresses [ "private" ] } "
137+ puts "#{ h . color ( "Public IP Address" , :cyan ) } : #{ server . addresses [ "public" ] [ 0 ] } "
138+ puts "#{ h . color ( "Private IP Address" , :cyan ) } : #{ server . addresses [ "private" ] [ 0 ] } "
130139 puts "#{ h . color ( "Password" , :cyan ) } : #{ server . password } "
131-
140+
132141 print "\n #{ h . color ( "Requesting server" , :magenta ) } "
133- saved_password = server . password
134142
135143 # wait for it to be ready to do stuff
136144 server . wait_for { print "." ; ready? }
137145
138- puts "\n Server ready, waiting 15 seconds to bootstrap."
139- sleep 15
146+ puts ( "\n " )
147+
148+ print "\n #{ h . color ( "Waiting for sshd" , :magenta ) } "
149+
150+ print ( "." ) until tcp_test_ssh ( server . addresses [ "public" ] [ 0 ] ) { sleep @initial_sleep_delay ||= 10 ; puts ( "done" ) }
140151
141- puts " \n Bootstrapping #{ h . color ( server . name , :bold ) } ..."
152+ bootstrap_for_node ( server ) . run
142153
143- ssh = Chef ::Knife ::Ssh . new
144- ssh . name_args = [ server . addresses [ "public" ] [ 0 ] , "/bin/bash /etc/install-chef && /usr/bin/chef-client -j /etc/chef/first-boot.json" ]
145- ssh . config [ :ssh_user ] = "root"
146- ssh . config [ :manual ] = true
147- ssh . config [ :ssh_password ] = saved_password
148- ssh . password = saved_password
149- ssh . run
154+ puts "\n "
155+ puts "#{ h . color ( "Instance ID" , :cyan ) } : #{ server . id } "
156+ puts "#{ h . color ( "Name" , :cyan ) } : #{ server . name } "
157+ puts "#{ h . color ( "Flavor" , :cyan ) } : #{ server . flavor_id } "
158+ puts "#{ h . color ( "Image" , :cyan ) } : #{ server . image_id } "
159+ puts "#{ h . color ( "Public IP Address" , :cyan ) } : #{ server . addresses [ "public" ] [ 0 ] } "
160+ puts "#{ h . color ( "Private IP Address" , :cyan ) } : #{ server . addresses [ "private" ] [ 0 ] } "
161+ puts "#{ h . color ( "Password" , :cyan ) } : #{ server . password } "
162+ puts "#{ h . color ( "Run List" , :cyan ) } : #{ @name_args . join ( ', ' ) } "
163+ end
150164
165+ def bootstrap_for_node ( server )
166+ bootstrap = Chef ::Knife ::Bootstrap . new
167+ bootstrap . name_args = [ server . addresses [ "public" ] [ 0 ] ]
168+ bootstrap . config [ :run_list ] = @name_args
169+ bootstrap . config [ :ssh_user ] = config [ :ssh_user ] || "root"
170+ bootstrap . config [ :ssh_password ] = server . password
171+ bootstrap . config [ :identity_file ] = config [ :identity_file ]
172+ bootstrap . config [ :chef_node_name ] = config [ :chef_node_name ] || server . id
173+ bootstrap . config [ :prerelease ] = config [ :prerelease ]
174+ bootstrap . config [ :distro ] = config [ :distro ]
175+ bootstrap . config [ :use_sudo ] = true
176+ bootstrap . config [ :template_file ] = config [ :template_file ]
177+ bootstrap . config [ :environment ] = config [ :environment ]
178+ bootstrap
151179 end
152180 end
153181 end
0 commit comments