ironfan 3.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +51 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +130 -0
- data/Gemfile +26 -0
- data/LICENSE.md +201 -0
- data/README.md +328 -0
- data/Rakefile +104 -0
- data/TODO.md +16 -0
- data/VERSION +1 -0
- data/chefignore +41 -0
- data/cluster_chef-knife.gemspec +123 -0
- data/cluster_chef.gemspec +111 -0
- data/config/client.rb +59 -0
- data/config/proxy.pac +12 -0
- data/config/ubuntu10.04-ironfan.erb +157 -0
- data/config/ubuntu11.10-ironfan.erb +145 -0
- data/ironfan.gemspec +121 -0
- data/lib/chef/knife/bootstrap/ubuntu10.04-ironfan.erb +157 -0
- data/lib/chef/knife/bootstrap/ubuntu11.10-ironfan.erb +145 -0
- data/lib/chef/knife/cluster_bootstrap.rb +74 -0
- data/lib/chef/knife/cluster_kick.rb +94 -0
- data/lib/chef/knife/cluster_kill.rb +73 -0
- data/lib/chef/knife/cluster_launch.rb +164 -0
- data/lib/chef/knife/cluster_list.rb +50 -0
- data/lib/chef/knife/cluster_proxy.rb +126 -0
- data/lib/chef/knife/cluster_show.rb +61 -0
- data/lib/chef/knife/cluster_ssh.rb +141 -0
- data/lib/chef/knife/cluster_start.rb +40 -0
- data/lib/chef/knife/cluster_stop.rb +43 -0
- data/lib/chef/knife/cluster_sync.rb +77 -0
- data/lib/chef/knife/generic_command.rb +66 -0
- data/lib/chef/knife/knife_common.rb +195 -0
- data/lib/ironfan.rb +143 -0
- data/lib/ironfan/chef_layer.rb +299 -0
- data/lib/ironfan/cloud.rb +412 -0
- data/lib/ironfan/cluster.rb +118 -0
- data/lib/ironfan/compute.rb +153 -0
- data/lib/ironfan/deprecated.rb +33 -0
- data/lib/ironfan/discovery.rb +177 -0
- data/lib/ironfan/dsl_object.rb +124 -0
- data/lib/ironfan/facet.rb +144 -0
- data/lib/ironfan/fog_layer.rb +150 -0
- data/lib/ironfan/private_key.rb +130 -0
- data/lib/ironfan/role_implications.rb +58 -0
- data/lib/ironfan/security_group.rb +119 -0
- data/lib/ironfan/server.rb +281 -0
- data/lib/ironfan/server_slice.rb +260 -0
- data/lib/ironfan/volume.rb +157 -0
- data/spec/ironfan/cluster_spec.rb +13 -0
- data/spec/ironfan/facet_spec.rb +69 -0
- data/spec/ironfan/server_slice_spec.rb +19 -0
- data/spec/ironfan/server_spec.rb +112 -0
- data/spec/ironfan_spec.rb +193 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/spec_helper/dummy_chef.rb +25 -0
- data/spec/test_config.rb +20 -0
- data/tasks/chef_config.rake +38 -0
- data/tasks/jeweler_use_alt_branch.rake +53 -0
- metadata +217 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Philip (flip) Kromer (<flip@infochimps.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Infochimps, 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 File.expand_path(File.dirname(__FILE__)+"/generic_command.rb")
|
20
|
+
|
21
|
+
class Chef
|
22
|
+
class Knife
|
23
|
+
class ClusterSync < Ironfan::Script
|
24
|
+
import_banner_and_options(Ironfan::Script)
|
25
|
+
|
26
|
+
option :cloud,
|
27
|
+
:long => "--[no-]cloud",
|
28
|
+
:description => "Sync to the cloud (default is yes, sync cloud; use --no-cloud to skip)",
|
29
|
+
:default => true,
|
30
|
+
:boolean => true
|
31
|
+
option :chef,
|
32
|
+
:long => "--[no-]chef",
|
33
|
+
:description => "Sync to the chef server (default is yes, sync chef; use --no-chef to skip)",
|
34
|
+
:default => true,
|
35
|
+
:boolean => true
|
36
|
+
option :sync_all,
|
37
|
+
:long => "--[no-]sync-all",
|
38
|
+
:description => "Sync, as best as possible, any defined node (even if it is missing from cloud or chef)",
|
39
|
+
:default => false,
|
40
|
+
:boolean => true
|
41
|
+
|
42
|
+
|
43
|
+
def relevant?(server)
|
44
|
+
if config[:sync_all]
|
45
|
+
not server.bogus?
|
46
|
+
else
|
47
|
+
server.created? && server.in_chef?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def perform_execution(target)
|
52
|
+
if config[:chef]
|
53
|
+
sync_to_chef target
|
54
|
+
else Chef::Log.debug("Skipping sync to chef") ; end
|
55
|
+
|
56
|
+
if config[:cloud] && target.any?(&:in_cloud?)
|
57
|
+
sync_to_cloud target
|
58
|
+
else Chef::Log.debug("Skipping sync to cloud") ; end
|
59
|
+
end
|
60
|
+
|
61
|
+
def sync_to_chef(target)
|
62
|
+
if config[:dry_run]
|
63
|
+
ui.info "(can't do a dry-run when syncing to chef -- skipping)"
|
64
|
+
return
|
65
|
+
end
|
66
|
+
ui.info "Syncing to Chef:"
|
67
|
+
target.sync_to_chef
|
68
|
+
end
|
69
|
+
|
70
|
+
def sync_to_cloud(target)
|
71
|
+
ui.info "Syncing to cloud:"
|
72
|
+
target.sync_to_cloud
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Philip (flip) Kromer (<flip@infochimps.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Infochimps, 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 File.expand_path(File.dirname(__FILE__)+"/knife_common.rb")
|
20
|
+
|
21
|
+
module Ironfan
|
22
|
+
class Script < Chef::Knife
|
23
|
+
include Ironfan::KnifeCommon
|
24
|
+
|
25
|
+
deps do
|
26
|
+
Ironfan::KnifeCommon.load_deps
|
27
|
+
end
|
28
|
+
|
29
|
+
option :dry_run,
|
30
|
+
:long => "--dry-run",
|
31
|
+
:description => "Don't really run, just use mock calls",
|
32
|
+
:boolean => true,
|
33
|
+
:default => false
|
34
|
+
option :yes,
|
35
|
+
:long => "--yes",
|
36
|
+
:description => "Skip confirmation prompts on risky actions.",
|
37
|
+
:boolean => true
|
38
|
+
|
39
|
+
def run
|
40
|
+
load_ironfan
|
41
|
+
die(banner) if @name_args.empty?
|
42
|
+
configure_dry_run
|
43
|
+
|
44
|
+
target = get_relevant_slice(* @name_args)
|
45
|
+
|
46
|
+
die("No nodes to #{sub_command}, exiting", 1) if target.empty?
|
47
|
+
|
48
|
+
ui.info(["\n",
|
49
|
+
ui.color("Running #{sub_command}", :cyan),
|
50
|
+
" on #{target.joined_names}..."].join())
|
51
|
+
unless config[:yes]
|
52
|
+
ui.info("")
|
53
|
+
confirm_execution(target)
|
54
|
+
end
|
55
|
+
#
|
56
|
+
perform_execution(target)
|
57
|
+
ui.info("")
|
58
|
+
ui.info "Finished! Current state:"
|
59
|
+
display(target)
|
60
|
+
end
|
61
|
+
|
62
|
+
def perform_execution(target)
|
63
|
+
target.send(sub_command)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
|
3
|
+
module Ironfan
|
4
|
+
module KnifeCommon
|
5
|
+
|
6
|
+
def self.load_deps
|
7
|
+
require 'formatador'
|
8
|
+
require 'chef/node'
|
9
|
+
require 'chef/api_client'
|
10
|
+
require 'fog'
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_ironfan
|
14
|
+
$LOAD_PATH << File.join(Chef::Config[:ironfan_path], '/lib') if Chef::Config[:ironfan_path]
|
15
|
+
require 'ironfan'
|
16
|
+
$stdout.sync = true
|
17
|
+
Ironfan.ui = self.ui
|
18
|
+
Ironfan.chef_config = self.config
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# A slice of a cluster:
|
23
|
+
#
|
24
|
+
# @param [String] cluster_name -- cluster to slice
|
25
|
+
# @param [String] facet_name -- facet to slice (or nil for all in cluster)
|
26
|
+
# @param [Array, String] slice_indexes -- servers in that facet (or nil for all in facet).
|
27
|
+
# You must specify a facet if you use slice_indexes.
|
28
|
+
#
|
29
|
+
# @return [Ironfan::ServerSlice] the requested slice
|
30
|
+
def get_slice(cluster_name, facet_name=nil, slice_indexes=nil)
|
31
|
+
if facet_name.nil? && slice_indexes.nil?
|
32
|
+
cluster_name, facet_name, slice_indexes = cluster_name.split(/[\s\-]/, 3)
|
33
|
+
end
|
34
|
+
ui.info("Inventorying servers in #{predicate_str(cluster_name, facet_name, slice_indexes)}")
|
35
|
+
cluster = Ironfan.load_cluster(cluster_name)
|
36
|
+
cluster.resolve!
|
37
|
+
cluster.discover!
|
38
|
+
cluster.slice(facet_name, slice_indexes)
|
39
|
+
end
|
40
|
+
|
41
|
+
def predicate_str(cluster_name, facet_name, slice_indexes)
|
42
|
+
[ "#{ui.color(cluster_name, :bold)} cluster",
|
43
|
+
(facet_name ? "#{ui.color(facet_name, :bold)} facet" : "#{ui.color("all", :bold)} facets"),
|
44
|
+
(slice_indexes ? "servers #{ui.color(slice_indexes, :bold)}" : "#{ui.color("all", :bold)} servers")
|
45
|
+
].join(', ')
|
46
|
+
end
|
47
|
+
|
48
|
+
# method to nodes should be filtered on
|
49
|
+
def relevant?(server)
|
50
|
+
server.exists?
|
51
|
+
end
|
52
|
+
|
53
|
+
# override in subclass to confirm risky actions
|
54
|
+
def confirm_execution(*args)
|
55
|
+
# pass
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Get a slice of nodes matching the given filter
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# target = get_relevant_slice(* @name_args)
|
63
|
+
#
|
64
|
+
def get_relevant_slice( *predicate )
|
65
|
+
full_target = get_slice( *predicate )
|
66
|
+
display(full_target) do |svr|
|
67
|
+
rel = relevant?(svr)
|
68
|
+
{ :relevant? => (rel ? "[blue]#{rel}[reset]" : '-' ) }
|
69
|
+
end
|
70
|
+
full_target.select{|svr| relevant?(svr) }
|
71
|
+
end
|
72
|
+
|
73
|
+
# passes target to ClusterSlice#display, will show headings in server slice
|
74
|
+
# tables based on the --verbose flag
|
75
|
+
def display(target, display_style=nil, &block)
|
76
|
+
display_style ||= (config[:verbosity] == 0 ? :default : :expanded)
|
77
|
+
target.display(display_style, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Put Fog into mock mode if --dry_run
|
82
|
+
#
|
83
|
+
def configure_dry_run
|
84
|
+
if config[:dry_run]
|
85
|
+
Fog.mock!
|
86
|
+
Fog::Mock.delay = 0
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Show a pretty progress bar while we wait for a set of threads to finish.
|
91
|
+
def progressbar_for_threads(threads)
|
92
|
+
section "Waiting for servers:"
|
93
|
+
total = threads.length
|
94
|
+
remaining = threads.select(&:alive?)
|
95
|
+
start_time = Time.now
|
96
|
+
until remaining.empty?
|
97
|
+
remaining = remaining.select(&:alive?)
|
98
|
+
if config[:verbose]
|
99
|
+
ui.info "waiting: #{total - remaining.length} / #{total}, #{(Time.now - start_time).to_i}s"
|
100
|
+
sleep 5
|
101
|
+
else
|
102
|
+
Formatador.redisplay_progressbar(total - remaining.length, total, {:started_at => start_time })
|
103
|
+
sleep 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# Collapse the threads
|
107
|
+
threads.each(&:join)
|
108
|
+
ui.info ''
|
109
|
+
end
|
110
|
+
|
111
|
+
def bootstrapper(server, hostname)
|
112
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
113
|
+
bootstrap.config.merge!(config)
|
114
|
+
|
115
|
+
bootstrap.name_args = [ hostname ]
|
116
|
+
bootstrap.config[:node] = server
|
117
|
+
bootstrap.config[:run_list] = server.combined_run_list
|
118
|
+
bootstrap.config[:ssh_user] = config[:ssh_user] || server.cloud.ssh_user
|
119
|
+
bootstrap.config[:attribute] = config[:attribute]
|
120
|
+
bootstrap.config[:identity_file] = config[:identity_file] || server.cloud.ssh_identity_file
|
121
|
+
bootstrap.config[:distro] = config[:distro] || server.cloud.bootstrap_distro
|
122
|
+
bootstrap.config[:use_sudo] = true unless config[:use_sudo] == false
|
123
|
+
bootstrap.config[:chef_node_name] = server.fullname
|
124
|
+
bootstrap.config[:client_key] = server.client_key.body if server.client_key.body
|
125
|
+
|
126
|
+
bootstrap
|
127
|
+
end
|
128
|
+
|
129
|
+
def run_bootstrap(node, hostname)
|
130
|
+
bs = bootstrapper(node, hostname)
|
131
|
+
if config[:skip].to_s == 'true'
|
132
|
+
ui.info "Skipping: bootstrapp #{hostname} with #{JSON.pretty_generate(bs.config)}"
|
133
|
+
return
|
134
|
+
end
|
135
|
+
begin
|
136
|
+
bs.run
|
137
|
+
rescue StandardError => e
|
138
|
+
ui.warn e
|
139
|
+
ui.warn e.backtrace
|
140
|
+
ui.warn ""
|
141
|
+
ui.warn node.inspect
|
142
|
+
ui.warn ""
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Utilities
|
148
|
+
#
|
149
|
+
|
150
|
+
def sub_command
|
151
|
+
self.class.sub_command
|
152
|
+
end
|
153
|
+
|
154
|
+
def confirm_or_exit question, correct_answer
|
155
|
+
response = ui.ask_question(question)
|
156
|
+
unless response.chomp == correct_answer
|
157
|
+
die "I didn't think so.", "Aborting!", 1
|
158
|
+
end
|
159
|
+
ui.info("")
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Announce a new section of tasks
|
164
|
+
#
|
165
|
+
def section(desc, *style)
|
166
|
+
style = [:blue] if style.empty?
|
167
|
+
ui.info(ui.color(desc, *style))
|
168
|
+
end
|
169
|
+
|
170
|
+
def die *args
|
171
|
+
Ironfan.die(*args)
|
172
|
+
end
|
173
|
+
|
174
|
+
module ClassMethods
|
175
|
+
def sub_command
|
176
|
+
self.to_s.gsub(/^.*::/, '').gsub(/^Cluster/, '').downcase
|
177
|
+
end
|
178
|
+
|
179
|
+
def import_banner_and_options(klass, options={})
|
180
|
+
options[:except] ||= []
|
181
|
+
deps{ klass.load_deps }
|
182
|
+
klass.options.sort.each do |name, info|
|
183
|
+
next if options.include?(name) || options[:except].include?(name)
|
184
|
+
option name, info
|
185
|
+
end
|
186
|
+
banner "knife cluster #{sub_command} CLUSTER_NAME [FACET_NAME [INDEXES]] (options)"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
def self.included(base)
|
190
|
+
base.class_eval do
|
191
|
+
extend ClassMethods
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/lib/ironfan.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'chef/mash'
|
2
|
+
require 'chef/config'
|
3
|
+
#
|
4
|
+
require 'gorillib/metaprogramming/class_attribute'
|
5
|
+
require 'gorillib/hash/reverse_merge'
|
6
|
+
require 'gorillib/object/blank'
|
7
|
+
require 'gorillib/hash/compact'
|
8
|
+
require 'set'
|
9
|
+
|
10
|
+
require 'ironfan/dsl_object'
|
11
|
+
require 'ironfan/cloud'
|
12
|
+
require 'ironfan/security_group'
|
13
|
+
require 'ironfan/compute' # base class for machine attributes
|
14
|
+
require 'ironfan/facet' # similar machines within a cluster
|
15
|
+
require 'ironfan/cluster' # group of machines with a common mission
|
16
|
+
require 'ironfan/server' # realization of a specific facet
|
17
|
+
require 'ironfan/discovery' # pair servers with Fog and Chef objects
|
18
|
+
require 'ironfan/server_slice' # collection of server objects
|
19
|
+
require 'ironfan/volume' # configure external and internal volumes
|
20
|
+
require 'ironfan/private_key' # coordinate chef keys, cloud keypairs, etc
|
21
|
+
require 'ironfan/role_implications' # make roles trigger other actions (security groups, etc)
|
22
|
+
#
|
23
|
+
require 'ironfan/chef_layer' # interface to chef for server actions
|
24
|
+
require 'ironfan/fog_layer' # interface to fog for server actions
|
25
|
+
#
|
26
|
+
require 'ironfan/deprecated' # stuff slated to go away
|
27
|
+
|
28
|
+
module Ironfan
|
29
|
+
|
30
|
+
# path to search for cluster definition files
|
31
|
+
def self.cluster_path
|
32
|
+
return Chef::Config[:cluster_path] if Chef::Config[:cluster_path]
|
33
|
+
raise "Holy smokes, you have no cookbook_path or cluster_path set up. Follow chef's directions for creating a knife.rb." if Chef::Config[:cookbook_path].blank?
|
34
|
+
cl_path = Chef::Config[:cookbook_path].map{|dir| File.expand_path('../clusters', dir) }.uniq
|
35
|
+
ui.warn "No cluster path set. Taking a wild guess that #{cl_path.inspect} is \nreasonable based on your cookbook_path -- but please set cluster_path in your knife.rb"
|
36
|
+
Chef::Config[:cluster_path] = cl_path
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Delegates
|
41
|
+
def self.clusters
|
42
|
+
Chef::Config[:clusters] ||= Mash.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.ui=(ui) @ui = ui ; end
|
46
|
+
def self.ui() @ui ; end
|
47
|
+
|
48
|
+
def self.chef_config=(cc) @chef_config = cc ; end
|
49
|
+
def self.chef_config() @chef_config ; end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Defines a cluster with the given name.
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# Ironfan.cluster 'demosimple' do
|
56
|
+
# cloud :ec2 do
|
57
|
+
# availability_zones ['us-east-1d']
|
58
|
+
# flavor "t1.micro"
|
59
|
+
# image_name "ubuntu-natty"
|
60
|
+
# end
|
61
|
+
# role :base_role
|
62
|
+
# role :chef_client
|
63
|
+
#
|
64
|
+
# facet :sandbox do
|
65
|
+
# instances 2
|
66
|
+
# role :nfs_client
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
#
|
71
|
+
def self.cluster(name, attrs={}, &block)
|
72
|
+
name = name.to_sym
|
73
|
+
cl = ( self.clusters[name] ||= Ironfan::Cluster.new(name, attrs) )
|
74
|
+
cl.configure(&block)
|
75
|
+
cl
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Return cluster if it's defined. Otherwise, search Ironfan.cluster_path
|
80
|
+
# for an eponymous file, load it, and return the cluster it defines.
|
81
|
+
#
|
82
|
+
# Raises an error if a matching file isn't found, or if loading that file
|
83
|
+
# doesn't define the requested cluster.
|
84
|
+
#
|
85
|
+
# @return [Ironfan::Cluster] the requested cluster
|
86
|
+
def self.load_cluster(cluster_name)
|
87
|
+
raise ArgumentError, "Please supply a cluster name" if cluster_name.to_s.empty?
|
88
|
+
return clusters[cluster_name] if clusters[cluster_name]
|
89
|
+
|
90
|
+
cluster_file = cluster_filenames[cluster_name] or die("Couldn't find a definition for #{cluster_name} in cluster_path: #{cluster_path.inspect}")
|
91
|
+
|
92
|
+
Chef::Log.info("Loading cluster #{cluster_file}")
|
93
|
+
|
94
|
+
require cluster_file
|
95
|
+
unless clusters[cluster_name] then die("#{cluster_file} was supposed to have the definition for the #{cluster_name} cluster, but didn't") end
|
96
|
+
|
97
|
+
clusters[cluster_name]
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Map from cluster name to file name
|
102
|
+
#
|
103
|
+
# @return [Hash] map from cluster name to file name
|
104
|
+
def self.cluster_filenames
|
105
|
+
return @cluster_filenames if @cluster_filenames
|
106
|
+
@cluster_filenames = {}
|
107
|
+
cluster_path.each do |cp_dir|
|
108
|
+
Dir[ File.join(cp_dir, '*.rb') ].each do |filename|
|
109
|
+
cluster_name = File.basename(filename).gsub(/\.rb$/, '')
|
110
|
+
@cluster_filenames[cluster_name] ||= filename
|
111
|
+
end
|
112
|
+
end
|
113
|
+
@cluster_filenames
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Utility to die with an error message.
|
118
|
+
# If the last arg is an integer, use it as the exit code.
|
119
|
+
#
|
120
|
+
def self.die *strings
|
121
|
+
exit_code = strings.last.is_a?(Integer) ? strings.pop : -1
|
122
|
+
strings.each{|str| ui.warn str }
|
123
|
+
exit exit_code
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Utility to turn an error into a warning
|
128
|
+
#
|
129
|
+
# @example
|
130
|
+
# Ironfan.safely do
|
131
|
+
# Ironfan.fog_connection.associate_address(self.fog_server.id, address)
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
def self.safely
|
135
|
+
begin
|
136
|
+
yield
|
137
|
+
rescue StandardError => boom
|
138
|
+
ui.info( boom )
|
139
|
+
Chef::Log.error( boom )
|
140
|
+
Chef::Log.error( boom.backtrace.join("\n") )
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|