cluster_chef-knife 3.0.5

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.
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
+