knife-rackspace 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/.chef/knife.rb ADDED
@@ -0,0 +1,18 @@
1
+ current_dir = File.dirname(__FILE__)
2
+ log_level :info
3
+ log_location STDOUT
4
+ #node_name "knife-rackspace"
5
+ #client_key "#{current_dir}/knife-rackspace.pem"
6
+ #validation_client_name "knife-rackspace-validator"
7
+ #validation_key "#{current_dir}/knife-rackspace-validator.pem"
8
+ #chef_server_url "https://api.opscode.com/organizations/knife-rackspace"
9
+ #cache_type 'basicfile'
10
+ #cache_options( :path => "#{ENV['home']}/.chef/checksums" )
11
+ #cookbook_path ["#{current_dir}/../cookbooks"]
12
+
13
+ knife[:rackspace_api_username] = "#{ENV['OS_USERNAME']}"
14
+ knife[:rackspace_api_key] = "#{ENV['OS_PASSWORD']}"
15
+
16
+ #https_proxy 'https://localhost:8888'
17
+ #knife[:ssl_verify_peer] = false
18
+
data/.gitignore CHANGED
@@ -14,6 +14,8 @@ spec/reports
14
14
  .config
15
15
  InstalledFiles
16
16
  .bundle
17
+ .rackspace_cloud_credentials*
18
+ *.lock
17
19
 
18
20
  # YARD artifacts
19
21
  .yardoc
data/.travis.yml ADDED
@@ -0,0 +1,28 @@
1
+ ---
2
+ language: ruby
3
+ rvm:
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - 1.8.7
7
+ env:
8
+ global:
9
+ - secure: ! 'eEsikE/p2yz7/cFvCN/NOoljxBqjzsTWnKC7iZ+fEGGsyhKVJgWn4AasBusZ
10
+
11
+ mhtQcOqwakRulzVc+EZN4pqWHgaMC7SnSwhqRU5u1E+BPI4iWNsu+7rjiXhE
12
+
13
+ cJK1vE8YodgsgRDQ6evZVQLkwJpRk0qq2tM2LDqpzvy2MeQJIGc='
14
+ - secure: ! 'V3ohIIF3I8lD05zTUs2nu+CcIzBYcvt1c1euRJ+SPcAH493b4cPquw1NZNFy
15
+
16
+ WDIW+1qDQi1DXr8C3Yq8bL4+pY66SukHtxyLhx0V26lGUvI8naq3IqageBQK
17
+
18
+ pkb2zZwVYO0a5mLEKOI/7omExBVHDxxX9Bw45vCHLKId3Wt21HE='
19
+ - secure: ! 'OyKQU1muVBijz2/EkLUgBWbwPKG2UqRhIfpB1ZFkfdQ/06+Vaf82ZEQk3DvY
20
+
21
+ 2lYFrzvSN1yWrKTMU3XES/dPlVlH87LDOrRhNhgW/NeHhC5BzdwrtvBm08RN
22
+
23
+ 64ACuFG8fch9zIbx2VTkyLV8xsFXPPSaKMbEXrnikLwqnl5M8PQ='
24
+ - secure: ! 'DoDVG0HwFg2a7Fj72tlxb8KJto+cBEh7J9YQZ35hK2cmeTsBAcZ4oSOUeAFg
25
+
26
+ s2MvBhX0man0zjYPStbL1vcpuS1Ecea3kGzefG0lYhHAzvK7wqqRqhKNFsnd
27
+
28
+ FPEVgZrcj3/Lc7mlkbigZouDM7BJLQbMGOv/KOS6f3nYzIhkOfE='
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## v0.7.0
2
+ * KNIFE_RACKSPACE-32 Ensure hint file is created to improve Ohai detection.
3
+ * KNIFE-181 correct mixed use of 'rackspace_auth_url' and 'rackspace_api_auth_url'. Only 'rackspace_auth_url' is correct.
4
+ * KNIFE-182 default to Rackspace Open Cloud (v2)
5
+ * KNIFE-267 Rackspace server create with networks
6
+ * KNIFE-271 Enable winrm authentication on knife-rackspace
7
+ * KNIFE-281 pass https_proxy and http_proxy setting onto fog; added ssl_verify_peer setting to disable certificate validation
8
+ * KNIFE-282 Add the ability to inject files on server creation
9
+ * KNIFE-289 Add Integration Tests
10
+
11
+ * KNOWN ISSUES: KNIFE-296 knife-windows overrides -x option with winrm-user
12
+
1
13
  ## v0.6.2
2
14
  * bump release to fix permission issues inside the gem
3
15
 
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ group :development, :test do
4
+ gem "knife-dsl", :git => 'git://github.com/maxlinc/knife-dsl.git', :branch => 'io_capture_fix'
5
+ end
6
+
3
7
  # Specify your gem's dependencies in knife-rackspace.gemspec
4
8
  gemspec
data/README.rdoc CHANGED
@@ -33,13 +33,13 @@ You also have the option of passing your Rackspace API Username/Key into the ind
33
33
  # provision a new 1GB Ubuntu 10.04 webserver
34
34
  knife rackspace server create -I 112 -f 3 -A 'Your Rackspace API username' -K "Your Rackspace API Key" -r 'role[webserver]'
35
35
 
36
- To select for the new OpenStack-based "Rackspace Open Cloud" API (aka 'v2'), you can use the <tt>--rackspace-version v2</tt> command option. 'v1' is still the default, so if you're using exclusively 'v2' you will probably want to add the following to your <tt>knife.rb</tt>:
36
+ To select for the previous Rackspace API (aka 'v1'), you can use the <tt>--rackspace-version v1</tt> command option. 'v2' is the default, so if you're still using exclusively 'v1' you will probably want to add the following to your <tt>knife.rb</tt>:
37
37
 
38
- knife[:rackspace_version] = 'v2'
38
+ knife[:rackspace_version] = 'v1'
39
39
 
40
40
  This plugin also has support for authenticating against an alternate API Auth URL. This is useful if you are a Rackspace Cloud UK user, here is an example of configuring your <tt>knife.rb</tt>:
41
41
 
42
- knife[:rackspace_api_auth_url] = "lon.auth.api.rackspacecloud.com"
42
+ knife[:rackspace_auth_url] = "lon.auth.api.rackspacecloud.com"
43
43
 
44
44
  This plugin also has support for specifying which region to create servers into:
45
45
 
@@ -50,6 +50,14 @@ valid options include:
50
50
  ORD_ENDPOINT = 'https://ord.servers.api.rackspacecloud.com/v2'
51
51
  LON_ENDPOINT = 'https://lon.servers.api.rackspacecloud.com/v2'
52
52
 
53
+ If you are behind a proxy you can specify it in the knife.rb file as follows:
54
+
55
+ https_proxy https://PROXY_IP_ADDRESS:PORT
56
+
57
+ SSL certificate verification can be disabled by include the following in your knife.rb file:
58
+
59
+ knife[:ssl_verify_peer] = false
60
+
53
61
  Additionally the following options may be set in your `knife.rb`:
54
62
 
55
63
  * flavor
@@ -67,6 +75,12 @@ Provisions a new server in the Rackspace Cloud and then perform a Chef bootstrap
67
75
 
68
76
  If no name is provided, nodes created with the v1 API are named after their instance ID, with the v2 API they are given a random 'rs-XXXXXXXXX' name.
69
77
 
78
+ Files can be injected onto the provisioned system using the <tt>--file</tt> switch. For example to inject <tt>my_script.sh</tt> into <tt>/root/initialize.sh</tt> you would use the following switch
79
+
80
+ --file /root/initialize.sh=my_script.sh.
81
+
82
+ Note: You can only inject text files and the maximum destination path is 255 characters.
83
+
70
84
  == knife rackspace server delete
71
85
 
72
86
  Deletes an existing server in the currently configured Rackspace Cloud account by the server/instance id. You can find the instance id by entering 'knife rackspace server list'. Please note - this does not delete the associated node and client objects from the Chef server unless you pass the <tt>-P</tt> or <tt>--purge</tt> command option. Using the <tt>--purge</tt> option with v2 nodes will attempt to delete the node and client by the name of the node.
data/Rakefile CHANGED
@@ -19,3 +19,33 @@
19
19
 
20
20
  require 'bundler'
21
21
  Bundler::GemHelper.install_tasks
22
+
23
+ require 'rspec/core/rake_task'
24
+ RSpec::Core::RakeTask.new(:spec)
25
+ task :default => [:credentials, :spec, 'integration:live']
26
+
27
+ task :credentials do
28
+ if ENV['TRAVIS_SECURE_ENV_VARS'] == 'false'
29
+ puts "Setting vars"
30
+ ENV['OS_USERNAME'] = '_RAX_USERNAME_'
31
+ ENV['OS_PASSWORD'] = '_RAX_PASSWORD_'
32
+ ENV['RS_TENANT_ID'] = '000000'
33
+ ENV['RS_CDN_TENANT_NAME'] = '_CDN-TENANT-NAME_'
34
+ end
35
+ fail "Not all required variables detected" unless ENV['OS_USERNAME'] && ENV['OS_PASSWORD'] && ENV['RS_CDN_TENANT_NAME'] && ENV['RS_TENANT_ID']
36
+ end
37
+
38
+ namespace :integration do
39
+ desc 'Run the integration tests'
40
+ RSpec::Core::RakeTask.new(:test) do |t|
41
+ t.pattern = 'spec/integration/**'
42
+ end
43
+
44
+ desc 'Run the integration tests live (no VCR cassettes)'
45
+ task :live do
46
+ unless ENV['TRAVIS'] == 'true' && ENV['TRAVIS_SECURE_ENV_VARS'] == 'false'
47
+ ENV['INTEGRATION_TESTS'] = 'live'
48
+ Rake::Task['integration:test'].invoke
49
+ end
50
+ end
51
+ end
@@ -17,7 +17,14 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.add_dependency "fog", "~> 1.6"
20
+ s.add_dependency "knife-windows"
20
21
  s.add_dependency "chef", ">= 0.10.10"
21
22
  s.require_paths = ["lib"]
22
23
 
24
+ # In Gemfile because I'm using a fork on Github. Hopefully pull request will be merged and a new gem will be released soon.
25
+ # s.add_development_dependency "knife-dsl"
26
+ s.add_development_dependency "rspec"
27
+ s.add_development_dependency "vcr"
28
+ s.add_development_dependency "ansi"
29
+ s.add_development_dependency "rake"
23
30
  end
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
- # Copyright:: Copyright (c) 2011 Opscode, Inc.
3
+ # Copyright:: Copyright (c) 2011-2013 Opscode, Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -50,57 +50,80 @@ class Chef
50
50
  option :rackspace_version,
51
51
  :long => '--rackspace-version VERSION',
52
52
  :description => 'Rackspace Cloud Servers API version',
53
- :default => "v1",
53
+ :default => "v2",
54
54
  :proc => Proc.new { |version| Chef::Config[:knife][:rackspace_version] = version }
55
55
 
56
- option :rackspace_api_auth_url,
57
- :long => "--rackspace-api-auth-url URL",
56
+ option :rackspace_auth_url,
57
+ :long => "--rackspace-auth-url URL",
58
58
  :description => "Your rackspace API auth url",
59
59
  :default => "auth.api.rackspacecloud.com",
60
- :proc => Proc.new { |url| Chef::Config[:knife][:rackspace_api_auth_url] = url }
60
+ :proc => Proc.new { |url| Chef::Config[:knife][:rackspace_auth_url] = url }
61
61
 
62
62
  option :rackspace_endpoint,
63
63
  :long => "--rackspace-endpoint URL",
64
64
  :description => "Your rackspace API endpoint",
65
65
  :default => "https://dfw.servers.api.rackspacecloud.com/v2",
66
66
  :proc => Proc.new { |url| Chef::Config[:knife][:rackspace_endpoint] = url }
67
+
68
+ option :file,
69
+ :long => '--file DESTINATION-PATH=SOURCE-PATH',
70
+ :description => 'File to inject on node',
71
+ :proc => Proc.new {|arg|
72
+ Chef::Config[:knife][:file] ||= []
73
+ Chef::Config[:knife][:file] << arg
74
+ }
67
75
  end
68
76
  end
69
77
 
70
78
  def connection
71
- Chef::Log.debug("version #{Chef::Config[:knife][:rackspace_version]}") #config file
72
- Chef::Log.debug("version #{config[:rackspace_version]}") #cli
79
+ Chef::Log.debug("version #{Chef::Config[:knife][:rackspace_version]} (config)")
80
+ Chef::Log.debug("version #{config[:rackspace_version]} (cli)")
73
81
  Chef::Log.debug("rackspace_api_key #{Chef::Config[:knife][:rackspace_api_key]}")
74
82
  Chef::Log.debug("rackspace_username #{Chef::Config[:knife][:rackspace_username]}")
75
83
  Chef::Log.debug("rackspace_api_username #{Chef::Config[:knife][:rackspace_api_username]}")
76
- Chef::Log.debug("rackspace_auth_url #{Chef::Config[:knife][:rackspace_auth_url]}")
77
- Chef::Log.debug("rackspace_auth_url #{config[:rackspace_api_auth_url]}")
78
- Chef::Log.debug("rackspace_endpoint #{Chef::Config[:knife][:rackspace_endpoint]}")
79
- Chef::Log.debug("rackspace_endpoint #{config[:rackspace_endpoint]}")
80
- if (Chef::Config[:knife][:rackspace_version] == 'v2') || (config[:rackspace_version] == 'v2')
84
+ Chef::Log.debug("rackspace_auth_url #{Chef::Config[:knife][:rackspace_auth_url]} (config)")
85
+ Chef::Log.debug("rackspace_auth_url #{config[:rackspace_auth_url]} (cli)")
86
+ Chef::Log.debug("rackspace_endpoint #{Chef::Config[:knife][:rackspace_endpoint]} (config)")
87
+ Chef::Log.debug("rackspace_endpoint #{config[:rackspace_endpoint]} (cli)")
88
+ if (Chef::Config[:knife][:rackspace_version] == 'v1') || (config[:rackspace_version] == 'v1')
89
+ Chef::Log.debug("rackspace v1")
81
90
  @connection ||= begin
82
- connection = Fog::Compute.new(
83
- :provider => 'Rackspace',
84
- :version => 'v2',
85
- :rackspace_api_key => Chef::Config[:knife][:rackspace_api_key],
86
- :rackspace_username => (Chef::Config[:knife][:rackspace_username] || Chef::Config[:knife][:rackspace_api_username]),
87
- :rackspace_auth_url => Chef::Config[:knife][:rackspace_api_auth_url] || config[:rackspace_api_auth_url],
88
- :rackspace_endpoint => Chef::Config[:knife][:rackspace_endpoint] || config[:rackspace_endpoint]
89
- )
91
+ connection = Fog::Compute.new(connection_params({
92
+ :version => 'v1'
93
+ }))
90
94
  end
91
95
  else
96
+ Chef::Log.debug("rackspace v2")
92
97
  @connection ||= begin
93
- connection = Fog::Compute.new(
94
- :provider => 'Rackspace',
95
- :version => 'v1',
96
- :rackspace_api_key => Chef::Config[:knife][:rackspace_api_key],
97
- :rackspace_username => (Chef::Config[:knife][:rackspace_username] || Chef::Config[:knife][:rackspace_api_username]),
98
- :rackspace_auth_url => Chef::Config[:knife][:rackspace_api_auth_url] || config[:rackspace_api_auth_url]
99
- )
98
+ connection = Fog::Compute.new(connection_params({
99
+ :version => 'v2',
100
+ :rackspace_endpoint => Chef::Config[:knife][:rackspace_endpoint] || config[:rackspace_endpoint]
101
+ }))
100
102
  end
101
103
  end
102
104
  end
103
105
 
106
+ def connection_params(options={})
107
+ hash = options.merge({
108
+ :provider => 'Rackspace',
109
+ :rackspace_api_key => Chef::Config[:knife][:rackspace_api_key],
110
+ :rackspace_username => (Chef::Config[:knife][:rackspace_username] || Chef::Config[:knife][:rackspace_api_username]),
111
+ :rackspace_auth_url => Chef::Config[:knife][:rackspace_auth_url] || config[:rackspace_auth_url]
112
+ })
113
+
114
+ hash[:connection_options] ||= {}
115
+ Chef::Log.debug("https_proxy #{ Chef::Config[:https_proxy] || "<not specified>"} (config)")
116
+ Chef::Log.debug("http_proxy #{ Chef::Config[:http_proxy] || "<not specified>"} (config)")
117
+ if Chef::Config.has_key?(:https_proxy) || Chef::Config.has_key?(:http_proxy)
118
+ hash[:connection_options] = {:proxy => Chef::Config[:https_proxy] || Chef::Config[:http_proxy] }
119
+ end
120
+ Chef::Log.debug("using proxy #{hash[:connection_options][:proxy] || "<none>"} (config)")
121
+ Chef::Log.debug("ssl_verify_peer #{Chef::Config[:knife].include?(:ssl_verify_peer) ? Chef::Config[:knife][:ssl_verify_peer] : "<not specified>"} (config)")
122
+ hash[:connection_options][:ssl_verify_peer] = Chef::Config[:knife][:ssl_verify_peer] if Chef::Config[:knife].include?(:ssl_verify_peer)
123
+
124
+ hash
125
+ end
126
+
104
127
  def locate_config_value(key)
105
128
  key = key.to_sym
106
129
  Chef::Config[:knife][key] || config[key]
@@ -134,7 +157,7 @@ class Chef
134
157
  @public_dns_name ||= begin
135
158
  Resolv.getname(ip_address)
136
159
  rescue
137
- "#{ip_address.gsub('.','-')}.static.cloud-ips.com"
160
+ "#{ip_address.gsub('.','-')}.static.cloud-ips.com" if ip_address
138
161
  end
139
162
  end
140
163
 
@@ -145,7 +168,7 @@ class Chef
145
168
  end
146
169
 
147
170
  def rackspace_api_version
148
- version = Chef::Config[:knife][:rackspace_version] || 'v1'
171
+ version = Chef::Config[:knife][:rackspace_version] || 'v2'
149
172
  version.downcase
150
173
  end
151
174
 
@@ -0,0 +1,46 @@
1
+ require 'chef/knife/rackspace_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class RackspaceNetworkCreate < Knife
6
+
7
+ include Knife::RackspaceBase
8
+
9
+ banner "knife rackspace network create (options)"
10
+
11
+ option :label,
12
+ :short => "-L LABEL",
13
+ :long => "--label LABEL",
14
+ :description => "Label for the network",
15
+ :required => true
16
+
17
+ option :cidr,
18
+ :short => "-C CIDR",
19
+ :long => "--cidr CIDR",
20
+ :description => "CIDR for the network",
21
+ :required => true
22
+
23
+ def run
24
+ if version_one?
25
+ ui.error "Networks are not supported in v1"
26
+ exit 1
27
+ else
28
+ networks_list = [
29
+ ui.color('Label', :bold),
30
+ ui.color('CIDR', :bold),
31
+ ui.color('ID', :bold)
32
+ ]
33
+ end
34
+ options = {}
35
+ [:cidr, :label].each do |key|
36
+ options[key] = config[key]
37
+ end
38
+ net = connection.networks.create(options)
39
+
40
+ msg_pair("Network ID", net.id)
41
+ msg_pair("Label", net.label)
42
+ msg_pair("CIDR", net.cidr)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ require 'chef/knife/rackspace_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class RackspaceNetworkDelete < Knife
6
+
7
+ include Knife::RackspaceBase
8
+
9
+ banner "knife rackspace network delete NETWORK_ID [NETWORK_ID] (options)"
10
+
11
+ def run
12
+ if version_one?
13
+ ui.error "Networks are not supported in v1"
14
+ exit 1
15
+ else
16
+ @name_args.each do |net_id|
17
+ network = connection.networks.get(net_id)
18
+ unless(network)
19
+ ui.error "Could not locate network: #{net_id}"
20
+ exit 1
21
+ end
22
+ msg_pair("Network ID", network.id)
23
+ msg_pair("Label", network.label)
24
+ msg_pair("CIDR", network.cidr)
25
+
26
+ puts "\n"
27
+ confirm("Do you really want to delete this network")
28
+
29
+ network.destroy
30
+
31
+ ui.warn("Deleted network #{network.id}")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ require 'chef/knife/rackspace_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class RackspaceNetworkList < Knife
6
+
7
+ include Knife::RackspaceBase
8
+
9
+ banner "knife rackspace network list (options)"
10
+
11
+ def run
12
+ if version_one?
13
+ ui.error "Networks are not supported in v1"
14
+ exit 1
15
+ else
16
+ networks_list = [
17
+ ui.color('Label', :bold),
18
+ ui.color('CIDR', :bold),
19
+ ui.color('ID', :bold)
20
+ ]
21
+ end
22
+ connection.networks.sort_by(&:id).each do |network|
23
+ networks_list << network.label
24
+ networks_list << network.cidr
25
+ networks_list << network.id.to_s
26
+ end
27
+ puts ui.list(networks_list, :uneven_columns_across, 3)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -18,12 +18,16 @@
18
18
  #
19
19
 
20
20
  require 'chef/knife/rackspace_base'
21
+ require 'chef/knife/winrm_base'
22
+ require 'chef/knife'
21
23
 
22
24
  class Chef
23
25
  class Knife
24
26
  class RackspaceServerCreate < Knife
25
27
 
26
28
  include Knife::RackspaceBase
29
+ include Chef::Knife::WinrmBase
30
+
27
31
 
28
32
  deps do
29
33
  require 'fog'
@@ -35,6 +39,8 @@ class Chef
35
39
 
36
40
  banner "knife rackspace server create (options)"
37
41
 
42
+ attr_accessor :initial_sleep_delay
43
+
38
44
  option :flavor,
39
45
  :short => "-f FLAVOR",
40
46
  :long => "--flavor FLAVOR",
@@ -123,12 +129,60 @@ class Chef
123
129
  :proc => Proc.new { |m| Chef::Config[:knife][:rackspace_metadata] = JSON.parse(m) },
124
130
  :default => ""
125
131
 
132
+ option :hint,
133
+ :long => "--hint HINT_NAME[=HINT_FILE]",
134
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
135
+ :proc => Proc.new { |h|
136
+ Chef::Config[:knife][:hints] ||= {}
137
+ name, path = h.split("=")
138
+ Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
139
+ }
140
+
126
141
  option :host_key_verify,
127
142
  :long => "--[no-]host-key-verify",
128
143
  :description => "Verify host key, enabled by default",
129
144
  :boolean => true,
130
145
  :default => true
131
146
 
147
+ option :default_networks,
148
+ :long => "--[no-]default-networks",
149
+ :description => "Include public and service networks, enabled by default",
150
+ :boolean => true,
151
+ :default => true
152
+
153
+ option :network,
154
+ :long => '--network [LABEL_OR_ID]',
155
+ :description => "Add private network. Use multiple --network options to specify multiple networks.",
156
+ :proc => Proc.new{ |name|
157
+ Chef::Config[:knife][:rackspace_networks] ||= []
158
+ (Chef::Config[:knife][:rackspace_networks] << name).uniq!
159
+ }
160
+
161
+ option :bootstrap_protocol,
162
+ :long => "--bootstrap-protocol protocol",
163
+ :description => "Protocol to bootstrap Windows servers. options: winrm",
164
+ :default => nil
165
+
166
+ option :server_create_timeout,
167
+ :long => "--server-create-timeout timeout",
168
+ :description => "How long to wait until the server is ready; default is 600 seconds",
169
+ :default => 600,
170
+ :proc => Proc.new { |v| Chef::Config[:knife][:server_create_timeouts] = v}
171
+
172
+ option :bootstrap_proxy,
173
+ :long => "--bootstrap-proxy PROXY_URL",
174
+ :description => "The proxy server for the node being bootstrapped",
175
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_proxy] = v }
176
+
177
+
178
+ def load_winrm_deps
179
+ require 'winrm'
180
+ require 'em-winrm'
181
+ require 'chef/knife/bootstrap_windows_winrm'
182
+ require 'chef/knife/core/windows_bootstrap_context'
183
+ require 'chef/knife/winrm'
184
+ end
185
+
132
186
  def tcp_test_ssh(hostname)
133
187
  tcp_socket = TCPSocket.new(hostname, 22)
134
188
  readable = IO.select([tcp_socket], nil, nil, 5)
@@ -153,6 +207,66 @@ class Chef
153
207
  tcp_socket && tcp_socket.close
154
208
  end
155
209
 
210
+
211
+ def parse_file_argument(arg)
212
+ dest, src = arg.split('=')
213
+ unless dest && src
214
+ ui.error "Unable to process file arguments #{arg}. The --file option requires both the destination on the remote machine as well as the local source be supplied using the form DESTINATION-PATH=SOURCE-PATH"
215
+ exit 1
216
+ end
217
+ [dest, src]
218
+ end
219
+
220
+ def encode_file(file)
221
+ begin
222
+ filename = File.expand_path(file)
223
+ content = File.read(filename)
224
+ rescue Errno::ENOENT => e
225
+ ui.error "Unable to read source file - #{filename}"
226
+ exit 1
227
+ end
228
+ Base64.encode64(content)
229
+ end
230
+
231
+ def files
232
+ return {} unless Chef::Config[:knife][:file]
233
+
234
+ files = []
235
+ Chef::Config[:knife][:file].each do |arg|
236
+ dest, src = parse_file_argument(arg)
237
+ Chef::Log.debug("Inject file #{src} into #{dest}")
238
+ files << {
239
+ :path => dest,
240
+ :contents => encode_file(src)
241
+ }
242
+ end
243
+ files
244
+ end
245
+
246
+
247
+
248
+ def tcp_test_winrm(hostname, port)
249
+ TCPSocket.new(hostname, port)
250
+ return true
251
+ rescue SocketError
252
+ sleep 2
253
+ false
254
+ rescue Errno::ETIMEDOUT
255
+ false
256
+ rescue Errno::EPERM
257
+ false
258
+ rescue Errno::ECONNREFUSED
259
+ sleep 2
260
+ false
261
+ rescue Errno::EHOSTUNREACH
262
+ sleep 2
263
+ false
264
+ rescue Errno::ENETUNREACH
265
+ sleep 2
266
+ false
267
+ end
268
+
269
+
156
270
  def run
157
271
  $stdout.sync = true
158
272
 
@@ -160,15 +274,24 @@ class Chef
160
274
  ui.error("You have not provided a valid image value. Please note the short option for this value recently changed from '-i' to '-I'.")
161
275
  exit 1
162
276
  end
163
-
277
+
278
+ if locate_config_value(:bootstrap_protocol) == 'winrm'
279
+ load_winrm_deps
280
+ end
281
+
164
282
  node_name = get_node_name(config[:chef_node_name] || config[:server_name])
283
+ networks = get_networks(Chef::Config[:knife][:rackspace_networks])
165
284
 
166
- server = connection.servers.create(
285
+ server = connection.servers.new(
167
286
  :name => node_name,
168
287
  :image_id => Chef::Config[:knife][:image],
169
288
  :flavor_id => locate_config_value(:flavor),
170
- :metadata => Chef::Config[:knife][:rackspace_metadata]
171
- )
289
+ :metadata => Chef::Config[:knife][:rackspace_metadata],
290
+ :personality => files
291
+ )
292
+ server.save(
293
+ :networks => networks
294
+ )
172
295
 
173
296
  msg_pair("Instance ID", server.id)
174
297
  msg_pair("Host ID", server.host_id)
@@ -176,11 +299,14 @@ class Chef
176
299
  msg_pair("Flavor", server.flavor.name)
177
300
  msg_pair("Image", server.image.name)
178
301
  msg_pair("Metadata", server.metadata)
302
+ if(networks && Chef::Config[:knife][:rackspace_networks])
303
+ msg_pair("Networks", Chef::Config[:knife][:rackspace_networks].sort.join(', '))
304
+ end
179
305
 
180
306
  print "\n#{ui.color("Waiting server", :magenta)}"
181
-
307
+
308
+ server.wait_for(Integer(locate_config_value(:server_create_timeout))) { print "."; ready? }
182
309
  # wait for it to be ready to do stuff
183
- server.wait_for { print "."; ready? }
184
310
 
185
311
  puts("\n")
186
312
 
@@ -188,9 +314,6 @@ class Chef
188
314
  msg_pair("Public IP Address", public_ip(server))
189
315
  msg_pair("Private IP Address", private_ip(server))
190
316
  msg_pair("Password", server.password)
191
-
192
- print "\n#{ui.color("Waiting for sshd", :magenta)}"
193
-
194
317
  #which IP address to bootstrap
195
318
  bootstrap_ip_address = public_ip(server)
196
319
  if config[:private_network]
@@ -202,11 +325,18 @@ class Chef
202
325
  exit 1
203
326
  end
204
327
 
328
+ if locate_config_value(:bootstrap_protocol) == 'winrm'
329
+ print "\n#{ui.color("Waiting for winrm", :magenta)}"
330
+ print(".") until tcp_test_winrm(bootstrap_ip_address, locate_config_value(:winrm_port))
331
+ bootstrap_for_windows_node(server, bootstrap_ip_address).run
332
+ else
333
+ print "\n#{ui.color("Waiting for sshd", :magenta)}"
205
334
  print(".") until tcp_test_ssh(bootstrap_ip_address) {
206
335
  sleep @initial_sleep_delay ||= 10
207
336
  puts("done")
208
337
  }
209
338
  bootstrap_for_node(server, bootstrap_ip_address).run
339
+ end
210
340
 
211
341
  puts "\n"
212
342
  msg_pair("Instance ID", server.id)
@@ -226,26 +356,45 @@ class Chef
226
356
  def bootstrap_for_node(server, bootstrap_ip_address)
227
357
  bootstrap = Chef::Knife::Bootstrap.new
228
358
  bootstrap.name_args = [bootstrap_ip_address]
229
- bootstrap.config[:run_list] = config[:run_list]
230
- bootstrap.config[:first_boot_attributes] = config[:first_boot_attributes]
231
359
  bootstrap.config[:ssh_user] = config[:ssh_user] || "root"
232
360
  bootstrap.config[:ssh_password] = server.password
233
361
  bootstrap.config[:identity_file] = config[:identity_file]
234
362
  bootstrap.config[:host_key_verify] = config[:host_key_verify]
363
+ # bootstrap will run as root...sudo (by default) also messes up Ohai on CentOS boxes
364
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
365
+ bootstrap_common_params(bootstrap, server)
366
+ end
367
+
368
+ def bootstrap_common_params(bootstrap, server)
369
+ bootstrap.config[:environment] = config[:environment]
370
+ bootstrap.config[:run_list] = config[:run_list]
235
371
  if version_one?
236
372
  bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.id
237
373
  else
238
- bootstrap.config[:chef_node_name] = server.name
374
+ bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.name
239
375
  end
240
376
  bootstrap.config[:prerelease] = config[:prerelease]
241
377
  bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
242
378
  bootstrap.config[:distro] = locate_config_value(:distro)
243
- # bootstrap will run as root...sudo (by default) also messes up Ohai on CentOS boxes
244
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
245
379
  bootstrap.config[:template_file] = locate_config_value(:template_file)
246
- bootstrap.config[:environment] = config[:environment]
380
+ bootstrap.config[:first_boot_attributes] = config[:first_boot_attributes]
381
+ bootstrap.config[:bootstrap_proxy] = locate_config_value(:bootstrap_proxy)
382
+ bootstrap.config[:encrypted_data_bag_secret] = config[:encrypted_data_bag_secret]
383
+ bootstrap.config[:encrypted_data_bag_secret_file] = config[:encrypted_data_bag_secret_file]
384
+ Chef::Config[:knife][:hints] ||= {}
385
+ Chef::Config[:knife][:hints]["rackspace"] ||= {}
247
386
  bootstrap
248
387
  end
388
+
389
+ def bootstrap_for_windows_node(server, bootstrap_ip_address)
390
+ bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
391
+ bootstrap.name_args = [bootstrap_ip_address]
392
+ bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) || 'Administrator'
393
+ bootstrap.config[:winrm_password] = locate_config_value(:winrm_password) || server.password
394
+ bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
395
+ bootstrap.config[:winrm_port] = locate_config_value(:winrm_port)
396
+ bootstrap_common_params(bootstrap, server)
397
+ end
249
398
 
250
399
  end
251
400
  #v2 servers require a name, random if chef_node_name is empty, empty if v1
@@ -254,5 +403,34 @@ class Chef
254
403
  #lazy uuids
255
404
  chef_node_name = "rs-"+rand.to_s.split('.')[1] unless version_one?
256
405
  end
406
+
407
+ def get_networks(names)
408
+ names = Array(names)
409
+ if(Chef::Config[:knife][:rackspace_version] == 'v2')
410
+ if(config[:default_networks])
411
+ nets = [
412
+ '00000000-0000-0000-0000-000000000000',
413
+ '11111111-1111-1111-1111-111111111111'
414
+ ]
415
+ else
416
+ nets = []
417
+ end
418
+ available_networks = connection.networks.all
419
+
420
+ names.each do |name|
421
+ net = available_networks.detect{|n| n.label == name || n.id == name}
422
+ if(net)
423
+ nets << net.id
424
+ else
425
+ ui.error("Failed to locate network: #{name}")
426
+ exit 1
427
+ end
428
+ end
429
+ nets
430
+ elsif(names && !names.empty?)
431
+ ui.error("Custom networks are only available in v2 API")
432
+ exit 1
433
+ end
434
+ end
257
435
  end
258
436
  end