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