cluster_chef-knife 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +51 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +63 -0
  4. data/Gemfile +18 -0
  5. data/LICENSE +201 -0
  6. data/README.md +332 -0
  7. data/Rakefile +92 -0
  8. data/TODO.md +8 -0
  9. data/VERSION +1 -0
  10. data/chefignore +41 -0
  11. data/cluster_chef-knife.gemspec +111 -0
  12. data/clusters/website_demo.rb +65 -0
  13. data/config/client.rb +59 -0
  14. data/lib/chef/knife/bootstrap/ubuntu10.04-basic.erb +78 -0
  15. data/lib/chef/knife/bootstrap/ubuntu10.04-cluster_chef.erb +139 -0
  16. data/lib/chef/knife/bootstrap/ubuntu11.10-cluster_chef.erb +128 -0
  17. data/lib/chef/knife/cluster_bootstrap.rb +69 -0
  18. data/lib/chef/knife/cluster_kick.rb +86 -0
  19. data/lib/chef/knife/cluster_kill.rb +73 -0
  20. data/lib/chef/knife/cluster_launch.rb +168 -0
  21. data/lib/chef/knife/cluster_list.rb +50 -0
  22. data/lib/chef/knife/cluster_proxy.rb +118 -0
  23. data/lib/chef/knife/cluster_show.rb +56 -0
  24. data/lib/chef/knife/cluster_ssh.rb +94 -0
  25. data/lib/chef/knife/cluster_start.rb +32 -0
  26. data/lib/chef/knife/cluster_stop.rb +37 -0
  27. data/lib/chef/knife/cluster_sync.rb +76 -0
  28. data/lib/chef/knife/generic_command.rb +66 -0
  29. data/lib/chef/knife/knife_common.rb +199 -0
  30. data/notes/aws_console_screenshot.jpg +0 -0
  31. data/rspec.watchr +29 -0
  32. data/spec/cluster_chef/cluster_spec.rb +13 -0
  33. data/spec/cluster_chef/facet_spec.rb +70 -0
  34. data/spec/cluster_chef/server_slice_spec.rb +19 -0
  35. data/spec/cluster_chef/server_spec.rb +112 -0
  36. data/spec/cluster_chef_spec.rb +193 -0
  37. data/spec/spec_helper/dummy_chef.rb +25 -0
  38. data/spec/spec_helper.rb +50 -0
  39. data/spec/test_config.rb +20 -0
  40. data/tasks/chef_config.rb +38 -0
  41. data/tasks/jeweler_use_alt_branch.rb +47 -0
  42. metadata +223 -0
@@ -0,0 +1,56 @@
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
+ class Chef
22
+ class Knife
23
+ class ClusterShow < Knife
24
+ include ClusterChef::KnifeCommon
25
+ deps do
26
+ ClusterChef::KnifeCommon.load_deps
27
+ end
28
+
29
+ banner "knife cluster show CLUSTER_NAME [FACET_NAME [INDEXES]] (options)"
30
+
31
+ def run
32
+ load_cluster_chef
33
+ die(banner) if @name_args.empty?
34
+ configure_dry_run
35
+
36
+ # Load the cluster/facet/slice/whatever
37
+ target = get_slice(* @name_args)
38
+
39
+ #
40
+ # Dump entire contents of objects if -VV flag given
41
+ #
42
+ if config[:verbosity] >= 2
43
+ target.each do |svr|
44
+ Chef::Log.debug( "Server #{svr.name}: #{JSON.pretty_generate(svr.to_hash)}" )
45
+ Chef::Log.debug( "- cloud: #{JSON.pretty_generate(svr.cloud.to_hash)}" )
46
+ Chef::Log.debug( "- fog: #{JSON.pretty_generate(svr.fog_description_for_launch)}" )
47
+ end
48
+ end
49
+
50
+ # Display same
51
+ display(target)
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,94 @@
1
+ #
2
+ # Author:: Chris Howe (<chris@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
+ require 'chef/knife/ssh'
21
+
22
+ class Chef
23
+ class Knife
24
+ class ClusterSsh < Chef::Knife::Ssh
25
+ include ClusterChef::KnifeCommon
26
+
27
+ deps do
28
+ Chef::Knife::Ssh.load_deps
29
+ ClusterChef::KnifeCommon.load_deps
30
+ end
31
+
32
+ banner 'knife cluster ssh "CLUSTER [FACET [INDEXES]]" COMMAND (options)'
33
+ Chef::Knife::Ssh.options.each do |name, hsh|
34
+ next if name == :attribute
35
+ option name, hsh
36
+ end
37
+
38
+ option :attribute,
39
+ :short => "-a ATTR",
40
+ :long => "--attribute ATTR",
41
+ :description => "The attribute to use for opening the connection - default is fqdn (ec2 users may prefer cloud.public_hostname)"
42
+
43
+ def configure_session
44
+ predicate = @name_args[0].split(/[\s\-]+/,3).map(&:strip)
45
+
46
+ target = get_slice(*predicate).select(&:sshable?)
47
+
48
+ display(target) if config[:verbose]
49
+
50
+ config[:attribute] ||= Chef::Config[:knife][:ssh_address_attribute] || "fqdn"
51
+ config[:ssh_user] ||= Chef::Config[:knife][:ssh_user]
52
+ config[:identity_file] ||= target.ssh_identity_file
53
+
54
+ @action_nodes = target.chef_nodes
55
+ addresses = target.servers.map do |svr|
56
+ if (svr.cloud.public_ip) then address = svr.cloud.public_ip ; end
57
+ if (not address) && (svr.fog_server) then address = svr.fog_server.public_ip_address ; end
58
+ if (not address) && (svr.chef_node) then address = format_for_display( svr.chef_node )[config[:attribute]] ; end
59
+ address
60
+ end.compact
61
+
62
+ (ui.fatal("No nodes returned from search!"); exit 10) if addresses.nil? || addresses.length == 0
63
+
64
+ session_from_list(addresses)
65
+ end
66
+
67
+ def cssh
68
+ exec "cssh "+session.servers_for.map {|server| server.user ? "#{server.user}@#{server.host}" : server.host}.join(" ")
69
+ end
70
+
71
+ def run
72
+ load_cluster_chef
73
+ die(banner) if @name_args.empty?
74
+ extend Chef::Mixin::Command
75
+
76
+ @longest = 0
77
+ configure_session
78
+
79
+ case @name_args[1]
80
+ when "interactive" then interactive
81
+ when "screen" then screen
82
+ when "tmux" then tmux
83
+ when "macterm" then macterm
84
+ when "cssh" then cssh
85
+ else
86
+ ssh_command(@name_args[1..-1].join(" "))
87
+ end
88
+
89
+ session.close
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,32 @@
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
+ require File.expand_path(File.dirname(__FILE__)+"/generic_command.rb")
21
+
22
+ class Chef
23
+ class Knife
24
+ class ClusterStart < ClusterChef::Script
25
+ import_banner_and_options(ClusterChef::Script)
26
+
27
+ def relevant?(server)
28
+ server.startable?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
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 ClusterStop < ClusterChef::Script
24
+ import_banner_and_options(ClusterChef::Script)
25
+
26
+ def relevant?(server)
27
+ server.running?
28
+ end
29
+
30
+ def confirm_execution(target)
31
+ ui.info " Unless these nodes are backed by EBS volumes, this will result in loss of all data"
32
+ ui.info " not saved elsewhere. Even if they are EBS backed, there may still be some data loss."
33
+ confirm_or_exit("Are you absolutely certain that you want to perform this action? (Type 'Yes' to confirm) ", 'Yes')
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,76 @@
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 < ClusterChef::Script
24
+ import_banner_and_options(ClusterChef::Script)
25
+
26
+ option :cloud,
27
+ :long => "--[no-]cloud",
28
+ :description => "Sync to the cloud (default syncs 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 syncs 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
+ if config[:cloud] && target.any?(&:in_cloud?)
56
+ sync_to_cloud target
57
+ else Chef::Log.debug("Skipping sync to cloud") ; end
58
+ end
59
+
60
+ def sync_to_chef(target)
61
+ if config[:dry_run]
62
+ ui.info "(can't do a dry-run when syncing to chef -- skipping)"
63
+ return
64
+ end
65
+ ui.info "Syncing to Chef:"
66
+ target.sync_to_chef
67
+ end
68
+
69
+ def sync_to_cloud(target)
70
+ ui.info "Syncing to cloud:"
71
+ target.sync_to_cloud
72
+ end
73
+
74
+ end
75
+ end
76
+ 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 ClusterChef
22
+ class Script < Chef::Knife
23
+ include ClusterChef::KnifeCommon
24
+
25
+ deps do
26
+ ClusterChef::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_cluster_chef
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,199 @@
1
+ require 'chef/knife'
2
+
3
+ module ClusterChef
4
+ module KnifeCommon
5
+
6
+ def self.load_deps
7
+ require 'readline'
8
+ require 'formatador'
9
+ require 'chef/node'
10
+ require 'chef/api_client'
11
+ require 'fog'
12
+ end
13
+
14
+ def load_cluster_chef
15
+ $LOAD_PATH << File.join(Chef::Config[:cluster_chef_path], '/lib') if Chef::Config[:cluster_chef_path]
16
+ require 'cluster_chef'
17
+ $stdout.sync = true
18
+ ClusterChef.ui = self.ui
19
+ ClusterChef.chef_config = self.config
20
+ end
21
+
22
+ #
23
+ # A slice of a cluster:
24
+ #
25
+ # @param [String] cluster_name -- cluster to slice
26
+ # @param [String] facet_name -- facet to slice (or nil for all in cluster)
27
+ # @param [Array, String] slice_indexes -- servers in that facet (or nil for all in facet).
28
+ # You must specify a facet if you use slice_indexes.
29
+ #
30
+ # @return [ClusterChef::ServerSlice] the requested slice
31
+ def get_slice(cluster_name, facet_name=nil, slice_indexes=nil)
32
+ if facet_name.nil? && slice_indexes.nil?
33
+ cluster_name, facet_name, slice_indexes = cluster_name.split(/[\s\-]/, 3)
34
+ end
35
+ ui.info("Inventorying servers in #{predicate_str(cluster_name, facet_name, slice_indexes)}")
36
+ cluster = ClusterChef.load_cluster(cluster_name)
37
+ cluster.resolve!
38
+ cluster.discover!
39
+ cluster.slice(facet_name, slice_indexes)
40
+ end
41
+
42
+ def predicate_str(cluster_name, facet_name, slice_indexes)
43
+ [ "#{ui.color(cluster_name, :bold)} cluster",
44
+ (facet_name ? "#{ui.color(facet_name, :bold)} facet" : "#{ui.color("all", :bold)} facets"),
45
+ (slice_indexes ? "servers #{ui.color(slice_indexes, :bold)}" : "#{ui.color("all", :bold)} servers")
46
+ ].join(', ')
47
+ end
48
+
49
+ # method to nodes should be filtered on
50
+ def relevant?(server)
51
+ server.exists?
52
+ end
53
+
54
+ # override in subclass to confirm risky actions
55
+ def confirm_execution(*args)
56
+ # pass
57
+ end
58
+
59
+ #
60
+ # Get a slice of nodes matching the given filter
61
+ #
62
+ # @example
63
+ # target = get_relevant_slice(* @name_args)
64
+ #
65
+ def get_relevant_slice( *predicate )
66
+ full_target = get_slice( *predicate )
67
+ display(full_target) do |svr|
68
+ rel = relevant?(svr)
69
+ { :relevant? => (rel ? "[blue]#{rel}[reset]" : '-' ) }
70
+ end
71
+ full_target.select{|svr| relevant?(svr) }
72
+ end
73
+
74
+ # passes target to ClusterSlice#display, will show headings in server slice
75
+ # tables based on the --verbose flag
76
+ def display(target, display_style=nil, &block)
77
+ display_style ||= (config[:verbosity] == 0 ? :default : :expanded)
78
+ target.display(display_style, &block)
79
+ end
80
+
81
+ #
82
+ # Put Fog into mock mode if --dry_run
83
+ #
84
+ def configure_dry_run
85
+ if config[:dry_run]
86
+ Fog.mock!
87
+ Fog::Mock.delay = 0
88
+ end
89
+ end
90
+
91
+ # Show a pretty progress bar while we wait for a set of threads to finish.
92
+ def progressbar_for_threads(threads)
93
+ section "Waiting for servers:"
94
+ total = threads.length
95
+ remaining = threads.select(&:alive?)
96
+ start_time = Time.now
97
+ until remaining.empty?
98
+ remaining = remaining.select(&:alive?)
99
+ if config[:verbose]
100
+ ui.info "waiting: #{total - remaining.length} / #{total}, #{(Time.now - start_time).to_i}s"
101
+ sleep 5
102
+ else
103
+ Formatador.redisplay_progressbar(total - remaining.length, total, {:started_at => start_time })
104
+ sleep 1
105
+ end
106
+ end
107
+ # Collapse the threads
108
+ threads.each(&:join)
109
+ ui.info ''
110
+ end
111
+
112
+ def bootstrapper(server, hostname)
113
+ bootstrap = Chef::Knife::Bootstrap.new
114
+ bootstrap.config.merge!(config)
115
+
116
+ bootstrap.name_args = [ hostname ]
117
+ bootstrap.config[:node] = server
118
+ bootstrap.config[:run_list] = server.combined_run_list
119
+ bootstrap.config[:ssh_user] = config[:ssh_user] || server.cloud.ssh_user
120
+ bootstrap.config[:attribute] = config[:attribute]
121
+ bootstrap.config[:identity_file] = config[:identity_file] || server.cloud.ssh_identity_file
122
+ bootstrap.config[:distro] = config[:distro] || server.cloud.bootstrap_distro
123
+ bootstrap.config[:use_sudo] = true unless config[:use_sudo] == false
124
+ bootstrap.config[:chef_node_name] = server.fullname
125
+ bootstrap.config[:client_key] = server.client_key.body if server.client_key.body
126
+
127
+ bootstrap
128
+ end
129
+
130
+ def run_bootstrap(node, hostname)
131
+ bs = bootstrapper(node, hostname)
132
+ if config[:skip].to_s == 'true'
133
+ ui.info "Skipping: bootstrapp #{hostname} with #{JSON.pretty_generate(bs.config)}"
134
+ return
135
+ end
136
+ begin
137
+ bs.run
138
+ rescue StandardError => e
139
+ ui.warn e
140
+ ui.warn e.backtrace
141
+ ui.warn ""
142
+ ui.warn node.inspect
143
+ ui.warn ""
144
+ end
145
+ end
146
+
147
+ #
148
+ # Utilities
149
+ #
150
+
151
+ def sub_command
152
+ self.class.sub_command
153
+ end
154
+
155
+ def confirm_or_exit question, correct_answer
156
+ response = ui.ask_question(question)
157
+ unless response.chomp == correct_answer
158
+ die "I didn't think so.", "Aborting!", 1
159
+ end
160
+ ui.info("")
161
+ end
162
+
163
+ #
164
+ # Announce a new section of tasks
165
+ #
166
+ def section(desc, *style)
167
+ style = [:blue] if style.empty?
168
+ ui.info(ui.color(desc, *style))
169
+ end
170
+
171
+ def die *args
172
+ ClusterChef.die(*args)
173
+ end
174
+
175
+ module ClassMethods
176
+ def sub_command
177
+ self.to_s.gsub(/^.*::/, '').gsub!(/^Cluster/, '').downcase
178
+ end
179
+
180
+ def import_banner_and_options(klass, options={})
181
+ options[:except] ||= []
182
+ klass.options.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
+
188
+ deps do
189
+ klass.load_deps
190
+ end
191
+ end
192
+ end
193
+ def self.included(base)
194
+ base.class_eval do
195
+ extend ClassMethods
196
+ end
197
+ end
198
+ end
199
+ end
Binary file
data/rspec.watchr ADDED
@@ -0,0 +1,29 @@
1
+ # -*- ruby -*-
2
+
3
+ def run_spec(file)
4
+ unless File.exist?(file)
5
+ puts "#{file} does not exist"
6
+ return
7
+ end
8
+
9
+ puts "Running #{file}"
10
+ system "rspec #{file}"
11
+ puts
12
+ end
13
+
14
+ watch("spec/.*/*_spec\.rb") do |match|
15
+ run_spec match[0]
16
+ end
17
+
18
+ watch("lib/(.*)\.rb") do |match|
19
+ file = %{spec/#{match[1]}_spec.rb}
20
+ run_spec file if File.exists?(file)
21
+ end
22
+
23
+ # watch('lib/cluster_chef/cookbook_munger\.rb') do |match|
24
+ # system match[0]
25
+ # end
26
+ #
27
+ # watch('lib/cluster_chef/cookbook_munger/.*\.erb') do |match|
28
+ # system 'lib/cluster_chef/cookbook_munger.rb'
29
+ # end
@@ -0,0 +1,13 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require CLUSTER_CHEF_DIR("lib/cluster_chef")
4
+
5
+ describe ClusterChef::Cluster do
6
+ describe 'discover!' do
7
+ let(:cluster){ get_example_cluster(:monkeyballs) }
8
+
9
+ it 'enumerates chef nodes' do
10
+ cluster.discover!
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,70 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require CLUSTER_CHEF_DIR("lib/cluster_chef")
4
+
5
+ describe ClusterChef::Facet do
6
+ let(:cluster){ ClusterChef.cluster(:gibbon) }
7
+ let(:facet){
8
+ cluster.facet(:namenode) do
9
+ instances 5
10
+ end
11
+ }
12
+
13
+ describe 'slicing' do
14
+ it 'has servers' do
15
+ facet.indexes.should == [0, 1, 2, 3, 4]
16
+ facet.valid_indexes.should == [0, 1, 2, 3, 4]
17
+ facet.server(3){ name(:bob) }
18
+ svrs = facet.servers
19
+ svrs.length.should == 5
20
+ svrs.map{|svr| svr.name }.should == ["gibbon-namenode-0", "gibbon-namenode-1", "gibbon-namenode-2", :bob, "gibbon-namenode-4"]
21
+ end
22
+
23
+ it 'servers have bogosity if out of range' do
24
+ facet.server(69).should be_bogus
25
+ facet.servers.select(&:bogus?).map(&:facet_index).should == [69]
26
+ facet.indexes.should == [0, 1, 2, 3, 4, 69]
27
+ facet.valid_indexes.should == [0, 1, 2, 3, 4]
28
+ end
29
+
30
+ it 'returns all on nil or "", but [] means none' do
31
+ facet.server(69)
32
+ facet.slice('' ).map(&:facet_index).should == [0, 1, 2, 3, 4, 69]
33
+ facet.slice(nil).map(&:facet_index).should == [0, 1, 2, 3, 4, 69]
34
+ facet.slice([] ).map(&:facet_index).should == []
35
+ end
36
+
37
+ it 'slice returns all by default' do
38
+ facet.server(69)
39
+ facet.slice().map(&:facet_index).should == [0, 1, 2, 3, 4, 69]
40
+ end
41
+
42
+ it 'with an array returns specified indexes (bogus or not) in sorted order' do
43
+ facet.server(69)
44
+ facet.slice( [3, 1, 0] ).map(&:facet_index).should == [0, 1, 3]
45
+ facet.slice( [3, 1, 69, 0] ).map(&:facet_index).should == [0, 1, 3, 69]
46
+ end
47
+
48
+ it 'with an array does not create new dummy servers' do
49
+ facet.server(69)
50
+ facet.slice( [3, 1, 69, 0, 75, 123] ).map(&:facet_index).should == [0, 1, 3, 69]
51
+ facet.has_server?(75).should be_false
52
+ facet.has_server?(69).should be_true
53
+ end
54
+
55
+ it 'with a string, converts to intervals' do
56
+ facet.slice('1' ).map(&:facet_index).should == [1]
57
+ facet.slice('5' ).map(&:facet_index).should == []
58
+ facet.slice('1-1' ).map(&:facet_index).should == [1]
59
+ facet.slice('0-1' ).map(&:facet_index).should == [0,1]
60
+ facet.slice('0-1,3-4').map(&:facet_index).should == [0,1,3,4]
61
+ facet.slice('0-1,69' ).map(&:facet_index).should == [0,1,69]
62
+ facet.slice('0-2,1-3').map(&:facet_index).should == [0,1,2,3]
63
+ facet.slice('3-1' ).map(&:facet_index).should == []
64
+ facet.slice('2-5' ).map(&:facet_index).should == [2,3,4]
65
+ facet.slice(1).map(&:facet_index).should == [1]
66
+ end
67
+
68
+ end
69
+ end
70
+