knife-rackspace 0.6.2 → 0.7.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/.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