knife-cloud 1.0.0.rc.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|