ironfan 3.1.0.rc1

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 (59) hide show
  1. data/.gitignore +51 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +130 -0
  4. data/Gemfile +26 -0
  5. data/LICENSE.md +201 -0
  6. data/README.md +328 -0
  7. data/Rakefile +104 -0
  8. data/TODO.md +16 -0
  9. data/VERSION +1 -0
  10. data/chefignore +41 -0
  11. data/cluster_chef-knife.gemspec +123 -0
  12. data/cluster_chef.gemspec +111 -0
  13. data/config/client.rb +59 -0
  14. data/config/proxy.pac +12 -0
  15. data/config/ubuntu10.04-ironfan.erb +157 -0
  16. data/config/ubuntu11.10-ironfan.erb +145 -0
  17. data/ironfan.gemspec +121 -0
  18. data/lib/chef/knife/bootstrap/ubuntu10.04-ironfan.erb +157 -0
  19. data/lib/chef/knife/bootstrap/ubuntu11.10-ironfan.erb +145 -0
  20. data/lib/chef/knife/cluster_bootstrap.rb +74 -0
  21. data/lib/chef/knife/cluster_kick.rb +94 -0
  22. data/lib/chef/knife/cluster_kill.rb +73 -0
  23. data/lib/chef/knife/cluster_launch.rb +164 -0
  24. data/lib/chef/knife/cluster_list.rb +50 -0
  25. data/lib/chef/knife/cluster_proxy.rb +126 -0
  26. data/lib/chef/knife/cluster_show.rb +61 -0
  27. data/lib/chef/knife/cluster_ssh.rb +141 -0
  28. data/lib/chef/knife/cluster_start.rb +40 -0
  29. data/lib/chef/knife/cluster_stop.rb +43 -0
  30. data/lib/chef/knife/cluster_sync.rb +77 -0
  31. data/lib/chef/knife/generic_command.rb +66 -0
  32. data/lib/chef/knife/knife_common.rb +195 -0
  33. data/lib/ironfan.rb +143 -0
  34. data/lib/ironfan/chef_layer.rb +299 -0
  35. data/lib/ironfan/cloud.rb +412 -0
  36. data/lib/ironfan/cluster.rb +118 -0
  37. data/lib/ironfan/compute.rb +153 -0
  38. data/lib/ironfan/deprecated.rb +33 -0
  39. data/lib/ironfan/discovery.rb +177 -0
  40. data/lib/ironfan/dsl_object.rb +124 -0
  41. data/lib/ironfan/facet.rb +144 -0
  42. data/lib/ironfan/fog_layer.rb +150 -0
  43. data/lib/ironfan/private_key.rb +130 -0
  44. data/lib/ironfan/role_implications.rb +58 -0
  45. data/lib/ironfan/security_group.rb +119 -0
  46. data/lib/ironfan/server.rb +281 -0
  47. data/lib/ironfan/server_slice.rb +260 -0
  48. data/lib/ironfan/volume.rb +157 -0
  49. data/spec/ironfan/cluster_spec.rb +13 -0
  50. data/spec/ironfan/facet_spec.rb +69 -0
  51. data/spec/ironfan/server_slice_spec.rb +19 -0
  52. data/spec/ironfan/server_spec.rb +112 -0
  53. data/spec/ironfan_spec.rb +193 -0
  54. data/spec/spec_helper.rb +50 -0
  55. data/spec/spec_helper/dummy_chef.rb +25 -0
  56. data/spec/test_config.rb +20 -0
  57. data/tasks/chef_config.rake +38 -0
  58. data/tasks/jeweler_use_alt_branch.rake +53 -0
  59. metadata +217 -0
@@ -0,0 +1,50 @@
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 ClusterList < Knife
24
+ include Ironfan::KnifeCommon
25
+
26
+ deps do
27
+ require 'formatador'
28
+ end
29
+
30
+ banner "knife cluster list (options)"
31
+
32
+ def run
33
+ load_ironfan
34
+ configure_dry_run
35
+
36
+ hash = Ironfan.cluster_filenames
37
+
38
+ table = []
39
+ hash.keys.sort.each do |key|
40
+ table.push( { :cluster => key, :path => hash[key] } )
41
+ end
42
+
43
+ ui.info "Cluster Path: #{ Ironfan.cluster_path.join ", " }"
44
+
45
+ Formatador.display_compact_table(table, [:cluster,:path])
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,126 @@
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__)+"/cluster_ssh.rb")
21
+ require File.expand_path(File.dirname(__FILE__)+"/generic_command.rb")
22
+
23
+ class Chef
24
+ class Knife
25
+ #
26
+ # Runs the ssh command to open a SOCKS proxy to the given host, and writes a
27
+ # PAC (automatic proxy config) file to
28
+ # /tmp/ironfan_proxy-YOURNAME.pac. Only the first host is used, even if
29
+ # multiple match.
30
+ #
31
+ # Why not use Net::Ssh directly? The SOCKS proxy support was pretty
32
+ # bad. Though ugly, exec'ing the command works.
33
+ #
34
+ class ClusterProxy < Ironfan::Script
35
+
36
+ import_banner_and_options(Chef::Knife::ClusterSsh, :except => [:concurrency, ])
37
+ banner 'knife cluster proxy "CLUSTER [FACET [INDEXES]]" (options) - Runs the ssh command to open a SOCKS proxy to the given host, and writes a PAC (automatic proxy config) file to /tmp/ironfan_proxy-YOURNAME.pac. Only the first host is used, even if multiple match.'
38
+
39
+ option :background,
40
+ :long => "--[no-]background",
41
+ :description => "Requests ssh to go to background after setting up the proxy",
42
+ :boolean => true,
43
+ :default => true
44
+
45
+ option :socks_port,
46
+ :long => '--socks-port',
47
+ :short => '-D',
48
+ :description => 'Port to listen on for SOCKS5 proxy',
49
+ :default => '6666'
50
+
51
+ def relevant?(server)
52
+ server.sshable?
53
+ end
54
+
55
+ def perform_execution(target)
56
+ svr = target.first
57
+ cmd = command_for_target(svr)
58
+
59
+ dump_proxy_pac
60
+ exec(*cmd)
61
+ end
62
+
63
+ def command_for_target(svr)
64
+ config[:attribute] ||= Chef::Config[:knife][:ssh_address_attribute] || "fqdn"
65
+ config[:ssh_user] ||= Chef::Config[:knife][:ssh_user]
66
+ config[:identity_file] ||= svr.cloud.ssh_identity_file
67
+ config[:host_key_verify] ||= Chef::Config[:knife][:host_key_verify] || (not config[:no_host_key_verify]) # pre-vs-post 0.10.4
68
+
69
+ if (svr.cloud.public_ip) then address = svr.cloud.public_ip ; end
70
+ if (not address) && (svr.chef_node) then address = format_for_display( svr.chef_node )[config[:attribute]] ; end
71
+ if (not address) && (svr.fog_server) then address = svr.fog_server.public_ip_address ; end
72
+
73
+ cmd = [ 'ssh', '-N' ]
74
+ cmd += [ '-D', config[:socks_port].to_s ]
75
+ cmd += [ '-p', config[:port].to_s ] if config[:port].present?
76
+ cmd << '-f' if config[:background]
77
+ cmd << "-#{'v' * config[:verbosity].to_i}" if (config[:verbosity].to_i > 0)
78
+ cmd += %w[ -o StrictHostKeyChecking=no ] if config[:host_key_verify]
79
+ cmd += %w[ -o ConnectTimeout=10 -o ServerAliveInterval=60 -o ControlPath=none ]
80
+ cmd += [ '-i', File.expand_path(config[:identity_file]) ] if config[:identity_file].present?
81
+ cmd << (config[:ssh_user] ? "#{config[:ssh_user]}@#{address}" : address)
82
+
83
+ Chef::Log.debug("Cluster proxy config: #{config.inspect}")
84
+ Chef::Log.debug("Cluster proxy command: #{cmd.inspect}")
85
+ ui.info(["SOCKS Proxy on",
86
+ "local port", ui.color(config[:socks_port], :cyan),
87
+ "for", ui.color(svr.name, :cyan),
88
+ "(#{address})"
89
+ ].join(" "))
90
+
91
+ cmd
92
+ end
93
+
94
+ #
95
+ # Write a .pac (automatic proxy configuration) file
96
+ # to /etc/ironfan_proxy-YOURNAME.pac
97
+ #
98
+ def dump_proxy_pac
99
+ pac_filename = File.expand_path(File.join('/tmp', "ironfan_proxy-#{ENV['USER']}.pac"))
100
+ ui.info("point your browser at PAC (automatic proxy config file) file://#{pac_filename}")
101
+ File.open(pac_filename, 'w') do |f|
102
+ f.print proxy_pac_contents
103
+ end
104
+ end
105
+
106
+ EC2_PROXY_PATTERNS = [ "*ec2*.amazonaws.com", "*ec2.internal*", "*compute-*.amazonaws.com", "*compute-*.internal*", "*domu*.internal*", "10.*",]
107
+
108
+ def proxy_pac_contents
109
+ proxy_patterns = EC2_PROXY_PATTERNS
110
+ proxy_patterns += Array(Chef::Config[:cluster_proxy_patterns])
111
+ rules = proxy_patterns.compact.map{|str| "(shExpMatch(host, %-28s))" % %Q{"#{str}"} }
112
+ %Q{function FindProxyForURL(url, host) {
113
+ if (#{rules.join(" ||\n ")}
114
+ ) {
115
+ return "SOCKS5 localhost:#{config[:socks_port]}";
116
+ }
117
+ return "DIRECT";
118
+ }\n}
119
+ end
120
+
121
+
122
+
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,61 @@
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 Ironfan::KnifeCommon
25
+ deps do
26
+ Ironfan::KnifeCommon.load_deps
27
+ end
28
+
29
+ banner "knife cluster show CLUSTER_NAME [FACET_NAME [INDEXES]] (options)"
30
+ option :cloud,
31
+ :long => "--[no-]cloud",
32
+ :description => "Look up machines on AWS cloud (default is yes, look up machines; use --no-cloud to skip)",
33
+ :default => true,
34
+ :boolean => true
35
+
36
+ def run
37
+ load_ironfan
38
+ die(banner) if @name_args.empty?
39
+ configure_dry_run
40
+
41
+ # Load the cluster/facet/slice/whatever
42
+ target = get_slice(* @name_args)
43
+
44
+ #
45
+ # Dump entire contents of objects if -VV flag given
46
+ #
47
+ if config[:verbosity] >= 2
48
+ target.each do |svr|
49
+ Chef::Log.debug( "Server #{svr.name}: #{JSON.pretty_generate(svr.to_hash)}" )
50
+ Chef::Log.debug( "- cloud: #{JSON.pretty_generate(svr.cloud.to_hash)}" )
51
+ Chef::Log.debug( "- fog: #{JSON.pretty_generate(svr.fog_launch_description)}" )
52
+ end
53
+ end
54
+
55
+ # Display same
56
+ display(target)
57
+
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,141 @@
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 Ironfan::KnifeCommon
26
+
27
+ deps do
28
+ Chef::Knife::Ssh.load_deps
29
+ Ironfan::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
+ #
68
+ # Override the one in Chef::Knife::Ssh to allow an err flag (prints in red
69
+ # if non-null)
70
+ #
71
+ def print_data(host, data, err=nil)
72
+ if data =~ /\n/
73
+ data.split(/\n/).each { |d| print_data(host, d, err) }
74
+ else
75
+ padding = @longest - host.length
76
+ str = ui.color(host, :cyan) + (" " * (padding + 1)) + (err ? ui.color(data, :red) : data)
77
+ ui.msg(str)
78
+ end
79
+ end
80
+
81
+ #
82
+ # Override the one in Chef::Knife::Ssh to give a big red warning if the
83
+ # process executes with badness
84
+ #
85
+ def ssh_command(command, subsession=nil)
86
+ subsession ||= session
87
+ command = fixup_sudo(command)
88
+ notifications = []
89
+ subsession.open_channel do |ch|
90
+ ch.request_pty
91
+ ch.exec command do |ch, success|
92
+ raise ArgumentError, "Cannot execute #{command}" unless success
93
+ # note: you can't do the stderr calback because requesting a pty
94
+ # squashes stderr and stdout together
95
+ ch.on_data do |ichannel, data|
96
+ print_data(ichannel[:host], data)
97
+ if data =~ /^knife sudo password: /
98
+ ichannel.send_data("#{get_password}\n")
99
+ end
100
+ end
101
+ ch.on_request "exit-status" do |ichannel, data|
102
+ exit_status = data.read_long
103
+ if exit_status != 0
104
+ command_snippet = (command.length < 70) ? command : (command[0..45] + ' ... ' + command[-19..-1])
105
+ notifications << [ichannel[:host], "'#{command_snippet.gsub(/[\r\n]+/, "; ")}' terminated with error status #{exit_status}", :err]
106
+ end
107
+ end
108
+ end
109
+ end
110
+ session.loop
111
+ notifications.each{|args| print_data(*args) }
112
+ end
113
+
114
+ def cssh
115
+ exec "cssh "+session.servers_for.map{|server| server.user ? "#{server.user}@#{server.host}" : server.host}.join(" ")
116
+ end
117
+
118
+ def run
119
+ load_ironfan
120
+ die(banner) if @name_args.empty?
121
+ extend Chef::Mixin::Command
122
+
123
+ @longest = 0
124
+ configure_session
125
+
126
+ case @name_args[1]
127
+ when "interactive" then interactive
128
+ when "screen" then screen
129
+ when "tmux" then tmux
130
+ when "macterm" then macterm
131
+ when "cssh" then cssh
132
+ else
133
+ ssh_command(@name_args[1..-1].join(" "))
134
+ end
135
+
136
+ session.close
137
+ end
138
+
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,40 @@
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 < Ironfan::Script
25
+ import_banner_and_options(Ironfan::Script)
26
+
27
+ def relevant?(server)
28
+ server.startable?
29
+ end
30
+
31
+ def perform_execution(target)
32
+ section("Starting machines")
33
+ super(target)
34
+ section("Announcing Chef nodes as started")
35
+ target.send(:delegate_to_servers, :announce_as_started)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
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
+ require File.expand_path(File.dirname(__FILE__)+"/generic_command.rb")
19
+
20
+ class Chef
21
+ class Knife
22
+ class ClusterStop < Ironfan::Script
23
+ import_banner_and_options(Ironfan::Script)
24
+
25
+ def relevant?(server)
26
+ server.running?
27
+ end
28
+
29
+ def perform_execution(target)
30
+ section("Stopping machines")
31
+ super(target)
32
+ section("Announcing Chef nodes as stopped")
33
+ target.send(:delegate_to_servers, :announce_as_stopped)
34
+ end
35
+
36
+ def confirm_execution(target)
37
+ ui.info " Unless these nodes are backed by EBS volumes, this will result in loss of all data"
38
+ ui.info " not saved elsewhere. Even if they are EBS backed, there may still be some data loss."
39
+ confirm_or_exit("Are you absolutely certain that you want to perform this action? (Type 'Yes' to confirm) ", 'Yes')
40
+ end
41
+ end
42
+ end
43
+ end