knife-cloud 1.0.0.rc.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.
- checksums.yaml +15 -0
- data/.gitignore +33 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +5 -0
- data/Gemfile +9 -0
- data/LICENSE +201 -0
- data/README.md +420 -0
- data/Rakefile +35 -0
- data/knife-cloud.gemspec +27 -0
- data/lib/chef/knife/cloud/chefbootstrap/bootstrap_distribution.rb +31 -0
- data/lib/chef/knife/cloud/chefbootstrap/bootstrap_options.rb +191 -0
- data/lib/chef/knife/cloud/chefbootstrap/bootstrap_protocol.rb +69 -0
- data/lib/chef/knife/cloud/chefbootstrap/bootstrapper.rb +78 -0
- data/lib/chef/knife/cloud/chefbootstrap/ssh_bootstrap_protocol.rb +179 -0
- data/lib/chef/knife/cloud/chefbootstrap/unix_distribution.rb +31 -0
- data/lib/chef/knife/cloud/chefbootstrap/windows_distribution.rb +32 -0
- data/lib/chef/knife/cloud/chefbootstrap/winrm_bootstrap_protocol.rb +85 -0
- data/lib/chef/knife/cloud/command.rb +101 -0
- data/lib/chef/knife/cloud/exceptions.rb +38 -0
- data/lib/chef/knife/cloud/fog/options.rb +29 -0
- data/lib/chef/knife/cloud/fog/service.rb +200 -0
- data/lib/chef/knife/cloud/helpers.rb +39 -0
- data/lib/chef/knife/cloud/list_resource_command.rb +97 -0
- data/lib/chef/knife/cloud/list_resource_options.rb +21 -0
- data/lib/chef/knife/cloud/server/create_command.rb +165 -0
- data/lib/chef/knife/cloud/server/create_options.rb +80 -0
- data/lib/chef/knife/cloud/server/delete_command.rb +68 -0
- data/lib/chef/knife/cloud/server/delete_options.rb +42 -0
- data/lib/chef/knife/cloud/server/list_command.rb +84 -0
- data/lib/chef/knife/cloud/server/list_options.rb +43 -0
- data/lib/chef/knife/cloud/server/options.rb +39 -0
- data/lib/chef/knife/cloud/server/show_command.rb +55 -0
- data/lib/chef/knife/cloud/server/show_options.rb +36 -0
- data/lib/chef/knife/cloud/service.rb +91 -0
- data/lib/knife-cloud/version.rb +6 -0
- data/lib/test/fixtures/knife.rb +9 -0
- data/lib/test/fixtures/validation.pem +27 -0
- data/lib/test/knife-utils/helper.rb +39 -0
- data/lib/test/knife-utils/knife_test_utils.rb +40 -0
- data/lib/test/knife-utils/matchers.rb +29 -0
- data/lib/test/knife-utils/test_bed.rb +56 -0
- data/lib/test/templates/chef-full-chef-zero.erb +67 -0
- data/lib/test/templates/windows-chef-client-msi.erb +231 -0
- data/lib/test/templates/windows-shell.erb +77 -0
- data/spec/resource_spec_helper.rb +49 -0
- data/spec/server_command_common_spec_helper.rb +48 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/shared_examples_for_command.rb +35 -0
- data/spec/support/shared_examples_for_servercreatecommand.rb +144 -0
- data/spec/support/shared_examples_for_serverdeletecommand.rb +77 -0
- data/spec/support/shared_examples_for_service.rb +85 -0
- data/spec/unit/bootstrap_protocol_spec.rb +70 -0
- data/spec/unit/bootstrapper_spec.rb +171 -0
- data/spec/unit/cloud_command_spec.rb +35 -0
- data/spec/unit/command_spec.rb +49 -0
- data/spec/unit/fog_service_spec.rb +138 -0
- data/spec/unit/list_resource_command_spec.rb +136 -0
- data/spec/unit/server_create_command_spec.rb +198 -0
- data/spec/unit/server_delete_command_spec.rb +25 -0
- data/spec/unit/server_list_command_spec.rb +119 -0
- data/spec/unit/server_show_command_spec.rb +64 -0
- data/spec/unit/service_spec.rb +46 -0
- data/spec/unit/ssh_bootstrap_protocol_spec.rb +116 -0
- data/spec/unit/unix_distribution_spec.rb +37 -0
- data/spec/unit/windows_distribution_spec.rb +37 -0
- data/spec/unit/winrm_bootstrap_protocol_spec.rb +106 -0
- 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
|
+
|