cluster_chef-knife 3.0.5
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 +63 -0
- data/Gemfile +18 -0
- data/LICENSE +201 -0
- data/README.md +332 -0
- data/Rakefile +92 -0
- data/TODO.md +8 -0
- data/VERSION +1 -0
- data/chefignore +41 -0
- data/cluster_chef-knife.gemspec +111 -0
- data/clusters/website_demo.rb +65 -0
- data/config/client.rb +59 -0
- data/lib/chef/knife/bootstrap/ubuntu10.04-basic.erb +78 -0
- data/lib/chef/knife/bootstrap/ubuntu10.04-cluster_chef.erb +139 -0
- data/lib/chef/knife/bootstrap/ubuntu11.10-cluster_chef.erb +128 -0
- data/lib/chef/knife/cluster_bootstrap.rb +69 -0
- data/lib/chef/knife/cluster_kick.rb +86 -0
- data/lib/chef/knife/cluster_kill.rb +73 -0
- data/lib/chef/knife/cluster_launch.rb +168 -0
- data/lib/chef/knife/cluster_list.rb +50 -0
- data/lib/chef/knife/cluster_proxy.rb +118 -0
- data/lib/chef/knife/cluster_show.rb +56 -0
- data/lib/chef/knife/cluster_ssh.rb +94 -0
- data/lib/chef/knife/cluster_start.rb +32 -0
- data/lib/chef/knife/cluster_stop.rb +37 -0
- data/lib/chef/knife/cluster_sync.rb +76 -0
- data/lib/chef/knife/generic_command.rb +66 -0
- data/lib/chef/knife/knife_common.rb +199 -0
- data/notes/aws_console_screenshot.jpg +0 -0
- data/rspec.watchr +29 -0
- data/spec/cluster_chef/cluster_spec.rb +13 -0
- data/spec/cluster_chef/facet_spec.rb +70 -0
- data/spec/cluster_chef/server_slice_spec.rb +19 -0
- data/spec/cluster_chef/server_spec.rb +112 -0
- data/spec/cluster_chef_spec.rb +193 -0
- data/spec/spec_helper/dummy_chef.rb +25 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/test_config.rb +20 -0
- data/tasks/chef_config.rb +38 -0
- data/tasks/jeweler_use_alt_branch.rb +47 -0
- 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
|
+
|