knife-cloud 1.0.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +33 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +11 -0
  5. data/CONTRIBUTING.md +5 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE +201 -0
  8. data/README.md +420 -0
  9. data/Rakefile +35 -0
  10. data/knife-cloud.gemspec +27 -0
  11. data/lib/chef/knife/cloud/chefbootstrap/bootstrap_distribution.rb +31 -0
  12. data/lib/chef/knife/cloud/chefbootstrap/bootstrap_options.rb +191 -0
  13. data/lib/chef/knife/cloud/chefbootstrap/bootstrap_protocol.rb +69 -0
  14. data/lib/chef/knife/cloud/chefbootstrap/bootstrapper.rb +78 -0
  15. data/lib/chef/knife/cloud/chefbootstrap/ssh_bootstrap_protocol.rb +179 -0
  16. data/lib/chef/knife/cloud/chefbootstrap/unix_distribution.rb +31 -0
  17. data/lib/chef/knife/cloud/chefbootstrap/windows_distribution.rb +32 -0
  18. data/lib/chef/knife/cloud/chefbootstrap/winrm_bootstrap_protocol.rb +85 -0
  19. data/lib/chef/knife/cloud/command.rb +101 -0
  20. data/lib/chef/knife/cloud/exceptions.rb +38 -0
  21. data/lib/chef/knife/cloud/fog/options.rb +29 -0
  22. data/lib/chef/knife/cloud/fog/service.rb +200 -0
  23. data/lib/chef/knife/cloud/helpers.rb +39 -0
  24. data/lib/chef/knife/cloud/list_resource_command.rb +97 -0
  25. data/lib/chef/knife/cloud/list_resource_options.rb +21 -0
  26. data/lib/chef/knife/cloud/server/create_command.rb +165 -0
  27. data/lib/chef/knife/cloud/server/create_options.rb +80 -0
  28. data/lib/chef/knife/cloud/server/delete_command.rb +68 -0
  29. data/lib/chef/knife/cloud/server/delete_options.rb +42 -0
  30. data/lib/chef/knife/cloud/server/list_command.rb +84 -0
  31. data/lib/chef/knife/cloud/server/list_options.rb +43 -0
  32. data/lib/chef/knife/cloud/server/options.rb +39 -0
  33. data/lib/chef/knife/cloud/server/show_command.rb +55 -0
  34. data/lib/chef/knife/cloud/server/show_options.rb +36 -0
  35. data/lib/chef/knife/cloud/service.rb +91 -0
  36. data/lib/knife-cloud/version.rb +6 -0
  37. data/lib/test/fixtures/knife.rb +9 -0
  38. data/lib/test/fixtures/validation.pem +27 -0
  39. data/lib/test/knife-utils/helper.rb +39 -0
  40. data/lib/test/knife-utils/knife_test_utils.rb +40 -0
  41. data/lib/test/knife-utils/matchers.rb +29 -0
  42. data/lib/test/knife-utils/test_bed.rb +56 -0
  43. data/lib/test/templates/chef-full-chef-zero.erb +67 -0
  44. data/lib/test/templates/windows-chef-client-msi.erb +231 -0
  45. data/lib/test/templates/windows-shell.erb +77 -0
  46. data/spec/resource_spec_helper.rb +49 -0
  47. data/spec/server_command_common_spec_helper.rb +48 -0
  48. data/spec/spec_helper.rb +25 -0
  49. data/spec/support/shared_examples_for_command.rb +35 -0
  50. data/spec/support/shared_examples_for_servercreatecommand.rb +144 -0
  51. data/spec/support/shared_examples_for_serverdeletecommand.rb +77 -0
  52. data/spec/support/shared_examples_for_service.rb +85 -0
  53. data/spec/unit/bootstrap_protocol_spec.rb +70 -0
  54. data/spec/unit/bootstrapper_spec.rb +171 -0
  55. data/spec/unit/cloud_command_spec.rb +35 -0
  56. data/spec/unit/command_spec.rb +49 -0
  57. data/spec/unit/fog_service_spec.rb +138 -0
  58. data/spec/unit/list_resource_command_spec.rb +136 -0
  59. data/spec/unit/server_create_command_spec.rb +198 -0
  60. data/spec/unit/server_delete_command_spec.rb +25 -0
  61. data/spec/unit/server_list_command_spec.rb +119 -0
  62. data/spec/unit/server_show_command_spec.rb +64 -0
  63. data/spec/unit/service_spec.rb +46 -0
  64. data/spec/unit/ssh_bootstrap_protocol_spec.rb +116 -0
  65. data/spec/unit/unix_distribution_spec.rb +37 -0
  66. data/spec/unit/windows_distribution_spec.rb +37 -0
  67. data/spec/unit/winrm_bootstrap_protocol_spec.rb +106 -0
  68. metadata +248 -0
@@ -0,0 +1,101 @@
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+ require "chef/knife/cloud/helpers"
21
+ require 'chef/knife/cloud/exceptions'
22
+
23
+ class Chef
24
+ class Knife
25
+
26
+ class Cloud
27
+ class Command < Chef::Knife
28
+ include Cloud::Helpers
29
+ attr_accessor :service, :custom_arguments
30
+
31
+ def run
32
+ begin
33
+ # Set dafult config
34
+ set_default_config
35
+
36
+ # validate compulsory params
37
+ validate!
38
+
39
+ # validate command pre-requisites (cli options)
40
+ validate_params!
41
+
42
+ # setup the service
43
+ @service = create_service_instance
44
+
45
+ service.ui = ui # for interactive user prompts/messages
46
+
47
+ # Perform any steps before handling the command
48
+ before_exec_command
49
+
50
+ # exec the actual cmd
51
+ execute_command
52
+
53
+ # Perform any steps after handling the command
54
+ after_exec_command
55
+ rescue CloudExceptions::KnifeCloudError => e
56
+ Chef::Log.debug(e.message)
57
+ exit 1
58
+ end
59
+ end
60
+
61
+ def create_service_instance
62
+ raise Chef::Exceptions::Override, "You must override create_service_instance in #{self.to_s} to create cloud specific service"
63
+ end
64
+
65
+ def execute_command
66
+ raise Chef::Exceptions::Override, "You must override execute_command in #{self.to_s}"
67
+ end
68
+
69
+ # Derived classes can override before_exec_command and after_exec_command
70
+ def before_exec_command
71
+ end
72
+
73
+ def after_exec_command
74
+ end
75
+
76
+ def set_default_config
77
+ end
78
+
79
+ def validate!(*keys)
80
+ # validates necessary options/params to carry out the command.
81
+ # subclasses to implement this.
82
+ errors = []
83
+ keys.each do |k|
84
+ errors << "You did not provide a valid '#{pretty_key(k)}' value." if locate_config_value(k).nil?
85
+ end
86
+ error_message = ""
87
+ raise CloudExceptions::ValidationError, error_message if errors.each{|e| ui.error(e); error_message = "#{error_message} #{e}."}.any?
88
+ end
89
+
90
+ def validate_params!
91
+ end
92
+
93
+ def pretty_key(key)
94
+ key.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(ssh)|(aws)/i) ? w.upcase : w.capitalize }
95
+ end
96
+
97
+ end # class Command
98
+ end
99
+ end
100
+ end
101
+
@@ -0,0 +1,38 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ class Chef
19
+ class Knife
20
+ class Cloud
21
+ class CloudExceptions
22
+ class KnifeCloudError < RuntimeError; end
23
+ class CloudAPIException < KnifeCloudError; end
24
+ class ServiceConnectionError < KnifeCloudError; end
25
+ class ValidationError < KnifeCloudError; end
26
+ class ServerCreateError < KnifeCloudError; end
27
+ class ServerSetupError < KnifeCloudError; end
28
+ class ServerDeleteError < KnifeCloudError; end
29
+ class ServerCreateDependenciesError < KnifeCloudError; end
30
+ class BootstrapError < KnifeCloudError; end
31
+ class ServerShowError < KnifeCloudError; end
32
+ class ChefServerError < KnifeCloudError; end
33
+ class NetworkNotFoundError < KnifeCloudError; end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,29 @@
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
4
+ #
5
+
6
+ class Chef
7
+ class Knife
8
+ class Cloud
9
+ module FogOptions
10
+
11
+ def self.included(includer)
12
+ includer.instance_eval do
13
+ option :fog_version,
14
+ :long => "--fog-version version",
15
+ :description => "Fog gem version to use. Use the ruby gem version strings",
16
+ :default => "",
17
+ :proc => Proc.new { |v| Chef::Config[:knife][:cloud_fog_version] = v}
18
+
19
+ option :api_endpoint,
20
+ :long => "--api-endpoint ENDPOINT",
21
+ :description => "Your API endpoint. Eg, for Eucalyptus it can be 'http://ecc.eucalyptus.com:8773/services/Eucalyptus'",
22
+ :proc => Proc.new { |endpoint| Chef::Config[:knife][:api_endpoint] = endpoint }
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,200 @@
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
+ # Author:: Prabhu Das (<prabhu.das@clogeny.com>)
4
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
5
+ #
6
+
7
+ require 'chef/knife/cloud/service'
8
+ require 'chef/knife/cloud/exceptions'
9
+
10
+ class Chef
11
+ class Knife
12
+ class Cloud
13
+ class FogService < Service
14
+
15
+ def initialize(options = {})
16
+ load_fog_gem
17
+ super
18
+ end
19
+
20
+ def load_fog_gem
21
+ begin
22
+ # Load specific version of fog. Any other classes/modules using fog are loaded after this.
23
+ gem "fog", Chef::Config[:knife][:cloud_fog_version]
24
+ require 'fog'
25
+ Chef::Log.debug("Using fog version: #{Gem.loaded_specs["fog"].version}")
26
+ rescue Exception => e
27
+ Chef::Log.error "Error loading fog gem."
28
+ exit 1
29
+ end
30
+ end
31
+
32
+ def connection
33
+ add_api_endpoint
34
+ @connection ||= begin
35
+ connection = Fog::Compute.new(@auth_params)
36
+ rescue Excon::Errors::Unauthorized => e
37
+ error_message = "Connection failure, please check your username and password."
38
+ ui.fatal(error_message)
39
+ raise CloudExceptions::ServiceConnectionError, "#{e.message}. #{error_message}"
40
+ rescue Excon::Errors::SocketError => e
41
+ error_message = "Connection failure, please check your authentication URL."
42
+ ui.fatal(error_message)
43
+ raise CloudExceptions::ServiceConnectionError, "#{e.message}. #{error_message}"
44
+ end
45
+ end
46
+
47
+ def network
48
+ @network ||= begin
49
+ network = Fog::Network.new(@auth_params)
50
+ rescue Excon::Errors::Unauthorized => e
51
+ error_message = "Connection failure, please check your username and password."
52
+ ui.fatal(error_message)
53
+ raise CloudExceptions::ServiceConnectionError, "#{e.message}. #{error_message}"
54
+ rescue Excon::Errors::SocketError => e
55
+ error_message = "Connection failure, please check your authentication URL."
56
+ ui.fatal(error_message)
57
+ raise CloudExceptions::ServiceConnectionError, "#{e.message}. #{error_message}"
58
+ rescue Fog::Errors::NotFound => e
59
+ error_message = "No Network service found. This command is unavailable with current cloud."
60
+ ui.fatal(error_message)
61
+ raise CloudExceptions::NetworkNotFoundError, "#{e.message}. #{error_message}"
62
+ end
63
+ end
64
+
65
+ # cloud server specific implementation methods for commands.
66
+ def create_server(options = {})
67
+ begin
68
+ add_custom_attributes(options[:server_def])
69
+ server = connection.servers.create(options[:server_def])
70
+ rescue Excon::Errors::BadRequest => e
71
+ response = Chef::JSONCompat.from_json(e.response.body)
72
+ if response['badRequest']['code'] == 400
73
+ message = "Bad request (400): #{response['badRequest']['message']}"
74
+ ui.fatal(message)
75
+ else
76
+ message = "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}"
77
+ ui.fatal(message)
78
+ end
79
+ raise CloudExceptions::ServerCreateError, message
80
+ rescue Fog::Errors::Error => e
81
+ raise CloudExceptions::ServerCreateError, e.message
82
+ end
83
+
84
+ print "\n#{ui.color("Waiting for server [wait time = #{options[:server_create_timeout]}]", :magenta)}"
85
+
86
+ # wait for it to be ready to do stuff
87
+ server.wait_for(Integer(options[:server_create_timeout])) { print "."; ready? }
88
+
89
+ puts("\n")
90
+ server
91
+ end
92
+
93
+ def delete_server(server_name)
94
+ begin
95
+ server = get_server(server_name)
96
+ msg_pair("Instance Name", get_server_name(server))
97
+ msg_pair("Instance ID", server.id)
98
+
99
+ puts "\n"
100
+ ui.confirm("Do you really want to delete this server")
101
+
102
+ # delete the server
103
+ server.destroy
104
+ rescue NoMethodError
105
+ error_message = "Could not locate server '#{server_name}'."
106
+ ui.error(error_message)
107
+ raise CloudExceptions::ServerDeleteError, error_message
108
+ rescue Excon::Errors::BadRequest => e
109
+ handle_excon_exception(CloudExceptions::ServerDeleteError, e)
110
+ end
111
+ end
112
+
113
+ ["servers", "images", "networks"].each do |resource_type|
114
+ define_method("list_#{resource_type}") do
115
+ begin
116
+ case resource_type
117
+ when "networks"
118
+ network.method(resource_type).call.all
119
+ else
120
+ connection.method(resource_type).call.all
121
+ end
122
+ rescue Excon::Errors::BadRequest => e
123
+ handle_excon_exception(CloudExceptions::CloudAPIException, e)
124
+ end
125
+ end
126
+ end
127
+
128
+ def handle_excon_exception(exception_class, e)
129
+ error_message = if e.response
130
+ response = Chef::JSONCompat.from_json(e.response.body)
131
+ "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}"
132
+ else
133
+ "Unknown server error : #{e.message}"
134
+ end
135
+ ui.fatal(error_message)
136
+ raise exception_class, error_message
137
+ end
138
+
139
+ def list_resource_configurations
140
+ begin
141
+ flavors = connection.flavors.all
142
+ rescue Excon::Errors::BadRequest => e
143
+ handle_excon_exception(CloudExceptions::CloudAPIException, e)
144
+ end
145
+ end
146
+
147
+ def delete_server_on_failure(server = nil)
148
+ server.destroy if ! server.nil?
149
+ end
150
+
151
+ def add_api_endpoint
152
+ raise Chef::Exceptions::Override, "You must override add_api_endpoint in #{self.to_s} to add endpoint in auth_params for connection"
153
+ end
154
+
155
+ def get_server_name(server)
156
+ server.name
157
+ end
158
+
159
+ def get_server(instance_id)
160
+ begin
161
+ server = connection.servers.get(instance_id)
162
+ rescue Excon::Errors::BadRequest => e
163
+ handle_excon_exception(CloudExceptions::KnifeCloudError, e)
164
+ end
165
+ end
166
+
167
+ def get_image(name_or_id)
168
+ connection.images.find{|img| img.name =~ /#{name_or_id}/ || img.id == name_or_id }
169
+ end
170
+
171
+ def get_flavor(name_or_id)
172
+ connection.flavors.find{|f| f.name == name_or_id || f.id == name_or_id }
173
+ end
174
+
175
+ def server_summary(server, columns_with_info = [])
176
+ # columns_with_info is array of hash with label, key and attribute extraction callback, ex [{:label => "Label text", :key => 'key', value => 'the_actual_value', value_callback => callback_method to extract/format the required value}, ...]
177
+ list = []
178
+ columns_with_info.each do |col_info|
179
+ value = if col_info[:value].nil?
180
+ (col_info[:value_callback].nil? ? server.send(col_info[:key]).to_s : col_info[:value_callback].call(server.send(col_info[:key])))
181
+ else
182
+ col_info[:value]
183
+ end
184
+ if !(value.nil? || value.empty?)
185
+ list << ui.color(col_info[:label], :bold)
186
+ list << value
187
+ end
188
+ end
189
+ puts ui.list(list, :uneven_columns_across, 2) if columns_with_info.length > 0
190
+ end
191
+
192
+ def is_image_windows?(image)
193
+ image_info = connection.images.get(image)
194
+ !image_info.nil? ? image_info.platform == 'windows' : false
195
+ end
196
+
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ class Chef
19
+ class Knife
20
+ class Cloud
21
+ module Helpers
22
+
23
+ # Additional helpers
24
+ def msg_pair(label, value, color=:cyan)
25
+ if value && !value.to_s.empty?
26
+ puts "#{ui.color(label, color)}: #{value}"
27
+ end
28
+ end
29
+
30
+ def locate_config_value(key)
31
+ key = key.to_sym
32
+ config[key] || Chef::Config[:knife][key]
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,97 @@
1
+ #
2
+ # Author:: Prabhu Das (<prabhu.das@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2013-14 Chef, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife/cloud/command'
20
+
21
+ class Chef
22
+ class Knife
23
+ class Cloud
24
+ class ResourceListCommand < Command
25
+
26
+ def initialize(argv=[])
27
+ super argv
28
+ # columns_with_info is array of hash with label, key and attribute extraction callback, ex [{:label => "Label text", :key => 'key', value_callback => callback_method to extract/format the required value}, ...]
29
+ @columns_with_info = []
30
+ @sort_by_field = "id" # default sort by id
31
+ end
32
+
33
+ def execute_command
34
+ # exec the cmd
35
+ resources = query_resource
36
+
37
+ # handle the response
38
+ list(resources)
39
+ end
40
+
41
+ def query_resource
42
+ # specific resource type must override this.
43
+ raise Chef::Exceptions::Override, "You must override query_resource in #{self.to_s} to return resources."
44
+ end
45
+
46
+ def is_resource_filtered?(attribute, value)
47
+ # resource_filters is array of filters in form {:attribute => attribute-name, :regex => 'filter regex value'}
48
+ return false if @resource_filters.nil?
49
+ @resource_filters.each do |filter|
50
+ if attribute == filter[:attribute] and value =~ filter[:regex]
51
+ return true
52
+ end
53
+ end
54
+ false
55
+ end
56
+
57
+ # Derived class can override this to add more functionality.
58
+ def get_resource_col_val(resource)
59
+ resource_filtered = false
60
+ list = []
61
+ @columns_with_info.each do |col_info|
62
+ value = (col_info[:value_callback].nil? ? resource.send(col_info[:key]).to_s : col_info[:value_callback].call(resource.send(col_info[:key])))
63
+ if !config[:disable_filter]
64
+ resource_filtered = true if is_resource_filtered?(col_info[:key], value)
65
+ end
66
+ list << value
67
+ end
68
+ return list unless resource_filtered
69
+ end
70
+
71
+ # When @columns_with_info is nil display all
72
+ def list(resources)
73
+ # display column wise only if @columns_with_info is specified, else as a json for readable display.
74
+ begin
75
+ resource_list = @columns_with_info.map { |col_info| ui.color(col_info[:label], :bold) } if @columns_with_info.length > 0
76
+ resources.sort_by{|x| x.send(@sort_by_field).downcase }.each do |resource|
77
+ if @columns_with_info.length > 0
78
+ list = get_resource_col_val(resource)
79
+ resource_list.concat(list) unless list.nil?
80
+ else
81
+ puts resource.to_json
82
+ puts "\n"
83
+ end
84
+ end
85
+
86
+ rescue => e
87
+ ui.fatal("Unknown resource error : #{e.message}")
88
+ raise e
89
+ end
90
+ puts ui.list(resource_list, :uneven_columns_across, @columns_with_info.length) if @columns_with_info.length > 0
91
+ end
92
+
93
+ end # class ResourceListCommand
94
+ end
95
+ end
96
+ end
97
+
@@ -0,0 +1,21 @@
1
+
2
+ class Chef
3
+ class Knife
4
+ class Cloud
5
+ module ResourceListOptions
6
+
7
+ def self.included(includer)
8
+ includer.class_eval do
9
+
10
+ option :disable_filter,
11
+ :long => "--disable-filter",
12
+ :description => "Disable filtering of the current resource listing.",
13
+ :boolean => true,
14
+ :default => false
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,165 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ require 'chef/knife/cloud/command'
18
+ require 'chef/knife/cloud/exceptions'
19
+ require 'chef/knife/cloud/chefbootstrap/bootstrapper'
20
+
21
+ class Chef
22
+ class Knife
23
+ class Cloud
24
+ class ServerCreateCommand < Command
25
+ attr_accessor :server, :create_options
26
+
27
+ def initialize(argv=[])
28
+ super argv
29
+ # columns_with_info is array of hash with label, key and attribute extraction callback, ex [{:label => "Label text", :key => 'key', value_callback => callback_method to extract/format the required value}, ...]
30
+ @columns_with_info = []
31
+ end
32
+
33
+ def validate_params!
34
+ # set param vm_name to a random value if the name is not set by the user (plugin)
35
+ config[:chef_node_name] = get_node_name(locate_config_value(:chef_node_name), locate_config_value(:chef_node_name_prefix))
36
+
37
+ # validate ssh_user, ssh_password, identity_file for ssh bootstrap protocol and winrm_password for winrm bootstrap protocol
38
+ errors = []
39
+
40
+ if locate_config_value(:bootstrap_protocol) == 'ssh'
41
+ if locate_config_value(:identity_file).nil? && locate_config_value(:ssh_password).nil?
42
+ errors << "You must provide either Identity file or SSH Password."
43
+ end
44
+ elsif locate_config_value(:bootstrap_protocol) == 'winrm'
45
+ if locate_config_value(:winrm_password).nil?
46
+ errors << "You must provide Winrm Password."
47
+ end
48
+ else
49
+ errors << "You must provide a valid bootstrap protocol. options [ssh/winrm]. For linux type images, options [ssh]"
50
+ end
51
+ error_message = ""
52
+ raise CloudExceptions::ValidationError, error_message if errors.each{|e| ui.error(e); error_message = "#{error_message} #{e}."}.any?
53
+ end
54
+
55
+ def before_exec_command
56
+ begin
57
+ post_connection_validations
58
+ service.create_server_dependencies
59
+ rescue CloudExceptions::ServerCreateDependenciesError => e
60
+ ui.fatal(e.message)
61
+ service.delete_server_dependencies
62
+ raise e
63
+ end
64
+ end
65
+
66
+ def execute_command
67
+ begin
68
+ @server = service.create_server(create_options)
69
+ rescue CloudExceptions::ServerCreateError => e
70
+ ui.fatal(e.message)
71
+ # server creation failed, so we need to rollback only dependencies.
72
+ service.delete_server_dependencies
73
+ raise e
74
+ end
75
+ service.server_summary(@server, @columns_with_info)
76
+ end
77
+
78
+ # Derived classes can override after_exec_command and also call cleanup_on_failure if any exception occured.
79
+ def after_exec_command
80
+ begin
81
+ # bootstrap the server
82
+ bootstrap
83
+ rescue CloudExceptions::BootstrapError => e
84
+ ui.fatal(e.message)
85
+ cleanup_on_failure
86
+ raise e
87
+ rescue => e
88
+ error_message = "Check if --bootstrap-protocol and --image-os-type is correct. #{e.message}"
89
+ ui.fatal(error_message)
90
+ cleanup_on_failure
91
+ raise e, error_message
92
+ end
93
+ end
94
+
95
+ def cleanup_on_failure
96
+ if config[:delete_server_on_failure]
97
+ service.delete_server_dependencies
98
+ service.delete_server_on_failure(@server)
99
+ end
100
+ end
101
+
102
+ # Bootstrap the server
103
+ def bootstrap
104
+ before_bootstrap
105
+ @bootstrapper = Bootstrapper.new(config)
106
+ Chef::Log.debug("Bootstrapping the server...")
107
+ ui.info("Bootstrapping the server by using #{ui.color("bootstrap_protocol", :cyan)}: #{config[:bootstrap_protocol]} and #{ui.color("image_os_type", :cyan)}: #{config[:image_os_type]}")
108
+ @bootstrapper.bootstrap
109
+ after_bootstrap
110
+ end
111
+
112
+ # any cloud specific initializations/cleanup we want to do around bootstrap.
113
+ def before_bootstrap
114
+ ssh_override_winrm if locate_config_value(:bootstrap_protocol) == 'ssh'
115
+ end
116
+
117
+ def after_bootstrap
118
+ service.server_summary(@server, @columns_with_info)
119
+ end
120
+
121
+ # knife-plugin can override set_default_config to set default config by using their own mechanism.
122
+ def set_default_config
123
+ config[:image_os_type] = 'windows' if config[:bootstrap_protocol] == 'winrm'
124
+ end
125
+
126
+ #generate a random name if chef_node_name is empty
127
+ def get_node_name(chef_node_name, prefix)
128
+ return chef_node_name unless chef_node_name.nil?
129
+ #lazy uuids
130
+ chef_node_name = "#{prefix}-"+rand.to_s.split('.')[1]
131
+ end
132
+
133
+ def post_connection_validations
134
+ end
135
+
136
+ private
137
+
138
+ # Here, ssh config override winrm config
139
+ def ssh_override_winrm
140
+ # unchanged ssh_user and changed winrm_user, override ssh_user
141
+ if locate_config_value(:ssh_user).eql?(options[:ssh_user][:default]) &&
142
+ !locate_config_value(:winrm_user).eql?(options[:winrm_user][:default])
143
+ config[:ssh_user] = locate_config_value(:winrm_user)
144
+ end
145
+ # unchanged ssh_port and changed winrm_port, override ssh_port
146
+ if locate_config_value(:ssh_port).eql?(options[:ssh_port][:default]) &&
147
+ !locate_config_value(:winrm_port).eql?(options[:winrm_port][:default])
148
+ config[:ssh_port] = locate_config_value(:winrm_port)
149
+ end
150
+ # unset ssh_password and set winrm_password, override ssh_password
151
+ if locate_config_value(:ssh_password).nil? &&
152
+ !locate_config_value(:winrm_password).nil?
153
+ config[:ssh_password] = locate_config_value(:winrm_password)
154
+ end
155
+ # unset identity_file and set kerberos_keytab_file, override identity_file
156
+ if locate_config_value(:identity_file).nil? &&
157
+ !locate_config_value(:kerberos_keytab_file).nil?
158
+ config[:identity_file] = locate_config_value(:kerberos_keytab_file)
159
+ end
160
+ end
161
+ end # class ServerCreateCommand
162
+ end
163
+ end
164
+ end
165
+