ironfan 4.3.4 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG.md +7 -0
  2. data/ELB.md +121 -0
  3. data/Gemfile +1 -0
  4. data/Rakefile +4 -0
  5. data/VERSION +1 -1
  6. data/ironfan.gemspec +48 -3
  7. data/lib/chef/knife/cluster_launch.rb +5 -0
  8. data/lib/chef/knife/cluster_proxy.rb +3 -3
  9. data/lib/chef/knife/cluster_sync.rb +4 -0
  10. data/lib/chef/knife/ironfan_knife_common.rb +17 -6
  11. data/lib/chef/knife/ironfan_script.rb +29 -11
  12. data/lib/ironfan.rb +2 -2
  13. data/lib/ironfan/broker/computer.rb +8 -3
  14. data/lib/ironfan/dsl/ec2.rb +133 -2
  15. data/lib/ironfan/headers.rb +4 -0
  16. data/lib/ironfan/provider.rb +48 -3
  17. data/lib/ironfan/provider/ec2.rb +23 -8
  18. data/lib/ironfan/provider/ec2/elastic_load_balancer.rb +239 -0
  19. data/lib/ironfan/provider/ec2/iam_server_certificate.rb +101 -0
  20. data/lib/ironfan/provider/ec2/machine.rb +8 -0
  21. data/lib/ironfan/provider/ec2/security_group.rb +3 -5
  22. data/lib/ironfan/requirements.rb +2 -0
  23. data/notes/Home.md +45 -0
  24. data/notes/INSTALL-cloud_setup.md +103 -0
  25. data/notes/INSTALL.md +134 -0
  26. data/notes/Ironfan-Roadmap.md +70 -0
  27. data/notes/advanced-superpowers.md +16 -0
  28. data/notes/aws_servers.jpg +0 -0
  29. data/notes/aws_user_key.png +0 -0
  30. data/notes/cookbook-versioning.md +11 -0
  31. data/notes/core_concepts.md +200 -0
  32. data/notes/declaring_volumes.md +3 -0
  33. data/notes/design_notes-aspect_oriented_devops.md +36 -0
  34. data/notes/design_notes-ci_testing.md +169 -0
  35. data/notes/design_notes-cookbook_event_ordering.md +249 -0
  36. data/notes/design_notes-meta_discovery.md +59 -0
  37. data/notes/ec2-pricing_and_capacity.md +69 -0
  38. data/notes/ec2-pricing_and_capacity.numbers +0 -0
  39. data/notes/homebase-layout.txt +102 -0
  40. data/notes/knife-cluster-commands.md +18 -0
  41. data/notes/named-cloud-objects.md +11 -0
  42. data/notes/opscode_org_key.png +0 -0
  43. data/notes/opscode_user_key.png +0 -0
  44. data/notes/philosophy.md +13 -0
  45. data/notes/rake_tasks.md +24 -0
  46. data/notes/renamed-recipes.txt +142 -0
  47. data/notes/silverware.md +85 -0
  48. data/notes/style_guide.md +300 -0
  49. data/notes/tips_and_troubleshooting.md +92 -0
  50. data/notes/version-3_2.md +273 -0
  51. data/notes/walkthrough-hadoop.md +168 -0
  52. data/notes/walkthrough-web.md +166 -0
  53. data/spec/fixtures/ec2/elb/snakeoil.crt +35 -0
  54. data/spec/fixtures/ec2/elb/snakeoil.key +51 -0
  55. data/spec/integration/minimal-chef-repo/chefignore +41 -0
  56. data/spec/integration/minimal-chef-repo/environments/_default.json +12 -0
  57. data/spec/integration/minimal-chef-repo/knife/credentials/knife-org.rb +19 -0
  58. data/spec/integration/minimal-chef-repo/knife/credentials/knife-user-ironfantester.rb +9 -0
  59. data/spec/integration/minimal-chef-repo/knife/knife.rb +66 -0
  60. data/spec/integration/minimal-chef-repo/roles/systemwide.rb +10 -0
  61. data/spec/integration/spec/elb_build_spec.rb +95 -0
  62. data/spec/integration/spec_helper.rb +16 -0
  63. data/spec/integration/spec_helper/launch_cluster.rb +55 -0
  64. data/spec/ironfan/ec2/elb_spec.rb +95 -0
  65. data/spec/ironfan/ec2/security_group_spec.rb +0 -6
  66. metadata +60 -3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # v4.4.0: @nickmarden adds ELB support, broader spec coverage
2
+ * Security groups: removing ensured field (never set, causing bug), making selection use VPC if applicable
3
+ * Spec coverage for full cluster launch process
4
+ * IAMServerCertificate and ElasticLoadBalancer support for EC2
5
+ * Support for cluster-wide resource aggregation
6
+ * Fixes a bug that occurs when you fat-finger a flavor name
7
+
1
8
  # v4.3.4:
2
9
  * whoops. actually want attributes, not compact_attributes (#191)
3
10
 
data/ELB.md ADDED
@@ -0,0 +1,121 @@
1
+ # Managing Elastic Load Balancers with Ironfan
2
+
3
+ ## Example
4
+
5
+ Ironfan.cluster "sparky" do
6
+
7
+ cloud(:ec2) do
8
+ # This certificate can be defined at a cluster or facet level.
9
+ # However, the last call wins if the same attribute is defined
10
+ # in more than one context.
11
+ iam_server_certificate "snake-oil" do
12
+ certificate IO.read('snakeoil.crt')
13
+ private_key IO.read('snakeoil.key')
14
+ certificate_chain IO.read('snakeoil.crt.bundle') # optional
15
+ end
16
+ end
17
+
18
+ facet :web do
19
+ instances 2
20
+ cloud(:ec2) do
21
+
22
+ elastic_load_balancer "sparky-elb" do
23
+ map_port('HTTP', 80, 'HTTP', 81)
24
+
25
+ # This SSL listener uses the certificate defined above
26
+ map_port('HTTPS', 443, 'HTTP', 81, 'snake-oil')
27
+
28
+ # Applies to all HTTPS/SSL listeners
29
+ disallowed_ciphers(%w[ RC4-SHA ])
30
+
31
+ # Health check that is made against ALL running instances
32
+ health_check do
33
+ ping_protocol 'HTTP'
34
+ ping_port 82
35
+ ping_path '/healthcheck'
36
+ timeout 4
37
+ interval 10
38
+ unhealthy_threshold 3
39
+ healthy_threshold 2
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+
47
+ ## Uploading your certificate
48
+
49
+ There are two ways to supply your certificate to Ironfan. The first is to make the certificate, private key, and (optionally) certificate chain data readable by Ruby code in your cluster file.
50
+
51
+ iam_server_certificate "snake-oil" do
52
+ certificate IO.read('snakeoil.crt')
53
+ private_key IO.read('snakeoil.key')
54
+ certificate_chain IO.read('snakeoil.crt.bundle') # optional
55
+ end
56
+
57
+ Having your server certificates persistently available in your ironfan-homebase might not be optimal, so you can simply specify the ARN of the existing IAM server certificate:
58
+
59
+ iam_server_certificate "snake-oil" do
60
+ arn 'arn:aws:iam::782698214375:server-certificate/ironfan-sparky-snake-oil'
61
+ end
62
+
63
+ ## When are Certificates and ELBs updated?
64
+
65
+ Your server certificates and ELBs are only relevant when there are server instances present to service them. Therefore, the following logic is applied to decide when to synchronize or update your certificate/ELB configuration:
66
+
67
+ ### bootstrap, kill, launch, start, stop, sync
68
+
69
+ When one of these `knife cluster` actions finishes, the Ironfan code will examine the up-to-date list of running servers to see if there are any ELBs that are now needed, or which are no longer needed. In support of these feature, any required certificate uploads, health check modifications, SSL policy updates, or listener modifications will be performed as well.
70
+
71
+ If an ELB is no longer needed, it will be destroyed **but any corresponding certificates will not be destroyed**. This is a convenience for system administrators who want to load their sensitive/precious certificates into AWS just once, and is intended to be used in conjunction with the `iam_server_certificate.arn` attribute.
72
+
73
+ (To get a list of existing ARNs, try the [IAM Command Line Toolkit](http://aws.amazon.com/developertools/AWS-Identity-and-Access-Management/4143) provided by AWS.)
74
+
75
+ ### kick, list, proxy, pry, show, ssh
76
+
77
+ These `knife cluster` commands are not associated with updates of the Chef or IAAS configurations of your server instances, so no certificate or ELB modifications occur after these commands complete.
78
+
79
+ ## SSL policy
80
+
81
+ The SSL policy control in Ironfan is very rudimentary. You may control which ciphers are explicitly disallowed as follows
82
+
83
+ elastic_load_balancer "sparky-elb" do
84
+ ...
85
+ disallowed_ciphers(%w[ RC4-SHA ])
86
+ ...
87
+ end
88
+
89
+ Note that the default behavior is to disallow ciphers that are hypothetically vulnerable to the [BEAST attack](http://vnhacker.blogspot.com/2011/09/beast.html). You probably don't want or need to change it.
90
+
91
+ ## How do port mappings work?
92
+
93
+ A port mapping is a mapping between a TCP port on the ELB and a TCP port on your instance. A request arriving at _external_port_number_ on your ELB will be negotiated using the _external_protocol_, and will be forwarded to the _internal_port_ on one of your instances using _internal_protocol_.
94
+
95
+ If _external_protocol_ is 'HTTPS' and _internal_protocol_ is 'HTTP' (which is referred to as "SSL termination" at the ELB), you'll probably want to know that in your server code. ELBs set the header [X-Forwarded-Proto](http://en.wikipedia.org/wiki/List_of_HTTP_header_fields) to let you know what protocol was used to connect to the ELB. Awareness of that header in your server allows you to aboid redirect loops if, for example, your code insists that clients connect over HTTPS.
96
+
97
+ The format of the map_port call is as follows:
98
+
99
+ map_port(external_protocol, external_port_number, internal_protocol, internal_port_number [, certificate])
100
+
101
+ You'll need a certificate if the _external\_protocol_ is 'HTTPS' or 'SSL'. It will be ignored otherwise.
102
+
103
+ Your one-and-only SSL policy (probably the BEAST-defeating default) will be applied to all port mappings with certificates.
104
+
105
+ ## Other policies
106
+
107
+ The Ironfan ELB code does not support other types of listener policies. Please contact me using the information below if you need other types of policy support.
108
+
109
+ ## VPC support
110
+
111
+ There is no explicit VPC support in this code. If you need to use Ironfan-based ELBs in a VPC context and find that this code doesn't give you what you need, please submit an issue or, better yet, a pull request.
112
+
113
+ ## Spanning facets
114
+
115
+ An ELB **can** be declared at the cluster level rather than the facet level, but that's a weird thing to do. But don't let me tell you not to be weird!
116
+
117
+ Note that since each ELB has only one health check, it would need to be the case that your servers in each facet could respond to the same health check request. If they can, they should probably be members of the same facet.
118
+
119
+ # Contact me
120
+
121
+ This feature was initially created by [Nick Marden](https://github.com/nickmarden). I'd love your feedback and suggestions.
data/Gemfile CHANGED
@@ -29,4 +29,5 @@ group :test do
29
29
  gem 'guard-rspec'
30
30
  gem 'guard-yard'
31
31
  gem 'ruby_gntp'
32
+ gem 'ruby-debug19'
32
33
  end
data/Rakefile CHANGED
@@ -72,6 +72,10 @@ RSpec::Core::RakeTask.new(:rcov) do |spec|
72
72
  spec.rcov_opts = %w[ --exclude .rvm --no-comments --text-summary ]
73
73
  end
74
74
 
75
+ RSpec::Core::RakeTask.new(:integration) do |spec|
76
+ spec.pattern = 'spec/integration/**/*_spec.rb'
77
+ end
78
+
75
79
  # ---------------------------------------------------------------------------
76
80
  #
77
81
  # Yard -- documentation
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.3.4
1
+ 4.4.0
data/ironfan.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ironfan"
8
- s.version = "4.3.4"
8
+ s.version = "4.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Infochimps"]
12
- s.date = "2012-10-11"
12
+ s.date = "2012-10-22"
13
13
  s.description = "Ironfan allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks."
14
14
  s.email = "coders@infochimps.com"
15
15
  s.extra_rdoc_files = [
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  ".rspec",
22
22
  ".yardopts",
23
23
  "CHANGELOG.md",
24
+ "ELB.md",
24
25
  "Gemfile",
25
26
  "Guardfile",
26
27
  "LICENSE.md",
@@ -77,6 +78,8 @@ Gem::Specification.new do |s|
77
78
  "lib/ironfan/provider/ec2.rb",
78
79
  "lib/ironfan/provider/ec2/ebs_volume.rb",
79
80
  "lib/ironfan/provider/ec2/elastic_ip.rb",
81
+ "lib/ironfan/provider/ec2/elastic_load_balancer.rb",
82
+ "lib/ironfan/provider/ec2/iam_server_certificate.rb",
80
83
  "lib/ironfan/provider/ec2/keypair.rb",
81
84
  "lib/ironfan/provider/ec2/machine.rb",
82
85
  "lib/ironfan/provider/ec2/placement_group.rb",
@@ -84,11 +87,53 @@ Gem::Specification.new do |s|
84
87
  "lib/ironfan/provider/virtualbox.rb",
85
88
  "lib/ironfan/provider/virtualbox/machine.rb",
86
89
  "lib/ironfan/requirements.rb",
90
+ "notes/Home.md",
91
+ "notes/INSTALL-cloud_setup.md",
92
+ "notes/INSTALL.md",
93
+ "notes/Ironfan-Roadmap.md",
94
+ "notes/advanced-superpowers.md",
95
+ "notes/aws_servers.jpg",
96
+ "notes/aws_user_key.png",
97
+ "notes/cookbook-versioning.md",
98
+ "notes/core_concepts.md",
99
+ "notes/declaring_volumes.md",
100
+ "notes/design_notes-aspect_oriented_devops.md",
101
+ "notes/design_notes-ci_testing.md",
102
+ "notes/design_notes-cookbook_event_ordering.md",
103
+ "notes/design_notes-meta_discovery.md",
104
+ "notes/ec2-pricing_and_capacity.md",
105
+ "notes/ec2-pricing_and_capacity.numbers",
106
+ "notes/homebase-layout.txt",
107
+ "notes/knife-cluster-commands.md",
108
+ "notes/named-cloud-objects.md",
109
+ "notes/opscode_org_key.png",
110
+ "notes/opscode_user_key.png",
111
+ "notes/philosophy.md",
112
+ "notes/rake_tasks.md",
113
+ "notes/renamed-recipes.txt",
114
+ "notes/silverware.md",
115
+ "notes/style_guide.md",
116
+ "notes/tips_and_troubleshooting.md",
117
+ "notes/version-3_2.md",
118
+ "notes/walkthrough-hadoop.md",
119
+ "notes/walkthrough-web.md",
87
120
  "spec/chef/cluster_bootstrap_spec.rb",
121
+ "spec/fixtures/ec2/elb/snakeoil.crt",
122
+ "spec/fixtures/ec2/elb/snakeoil.key",
88
123
  "spec/fixtures/gunbai.rb",
89
124
  "spec/fixtures/gunbai_slice.json",
125
+ "spec/integration/minimal-chef-repo/chefignore",
126
+ "spec/integration/minimal-chef-repo/environments/_default.json",
127
+ "spec/integration/minimal-chef-repo/knife/credentials/knife-org.rb",
128
+ "spec/integration/minimal-chef-repo/knife/credentials/knife-user-ironfantester.rb",
129
+ "spec/integration/minimal-chef-repo/knife/knife.rb",
130
+ "spec/integration/minimal-chef-repo/roles/systemwide.rb",
131
+ "spec/integration/spec/elb_build_spec.rb",
132
+ "spec/integration/spec_helper.rb",
133
+ "spec/integration/spec_helper/launch_cluster.rb",
90
134
  "spec/ironfan/cluster_spec.rb",
91
135
  "spec/ironfan/ec2/cloud_provider_spec.rb",
136
+ "spec/ironfan/ec2/elb_spec.rb",
92
137
  "spec/ironfan/ec2/security_group_spec.rb",
93
138
  "spec/spec_helper.rb",
94
139
  "spec/spec_helper/dummy_chef.rb",
@@ -100,7 +145,7 @@ Gem::Specification.new do |s|
100
145
  s.require_paths = ["lib"]
101
146
  s.rubygems_version = "1.8.24"
102
147
  s.summary = "Ironfan allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks."
103
- s.test_files = ["spec/spec_helper/dummy_chef.rb", "spec/ironfan/cluster_spec.rb", "spec/ironfan/ec2/cloud_provider_spec.rb", "spec/ironfan/ec2/security_group_spec.rb", "spec/chef/cluster_bootstrap_spec.rb", "spec/fixtures/gunbai_slice.json", "spec/fixtures/gunbai.rb", "spec/spec_helper.rb", "spec/test_config.rb"]
148
+ s.test_files = ["spec/spec_helper/dummy_chef.rb", "spec/integration/spec_helper/launch_cluster.rb", "spec/integration/minimal-chef-repo/roles/systemwide.rb", "spec/integration/minimal-chef-repo/environments/_default.json", "spec/integration/minimal-chef-repo/chefignore", "spec/integration/minimal-chef-repo/knife/knife.rb", "spec/integration/minimal-chef-repo/knife/credentials/knife-user-ironfantester.rb", "spec/integration/minimal-chef-repo/knife/credentials/knife-org.rb", "spec/integration/spec/elb_build_spec.rb", "spec/integration/spec_helper.rb", "spec/ironfan/cluster_spec.rb", "spec/ironfan/ec2/cloud_provider_spec.rb", "spec/ironfan/ec2/elb_spec.rb", "spec/ironfan/ec2/security_group_spec.rb", "spec/chef/cluster_bootstrap_spec.rb", "spec/fixtures/gunbai_slice.json", "spec/fixtures/ec2/elb/snakeoil.key", "spec/fixtures/ec2/elb/snakeoil.crt", "spec/fixtures/gunbai.rb", "spec/spec_helper.rb", "spec/test_config.rb"]
104
149
 
105
150
  if s.respond_to? :specification_version then
106
151
  s.specification_version = 3
@@ -88,6 +88,11 @@ class Chef
88
88
  perform_after_launch_tasks(computer)
89
89
  end
90
90
 
91
+ if healthy?
92
+ ui.info "Applying aggregations:"
93
+ all_computers(*@name_args).aggregate
94
+ end
95
+
91
96
  display(target)
92
97
  end
93
98
 
@@ -119,9 +119,9 @@ class Chef
119
119
  }\n}
120
120
  end
121
121
 
122
-
123
-
124
-
122
+ def aggregates?
123
+ false
124
+ end
125
125
  end
126
126
  end
127
127
  end
@@ -64,6 +64,10 @@ class Chef
64
64
  else Chef::Log.debug("Skipping sync to cloud") ; end
65
65
  end
66
66
 
67
+ def aggregates_on_noop?
68
+ true
69
+ end
70
+
67
71
  end
68
72
  end
69
73
  end
@@ -31,12 +31,7 @@ module Ironfan
31
31
  #
32
32
  # @return [Ironfan::ServerSlice] the requested slice
33
33
  def get_slice(slice_string, *args)
34
- if not args.empty?
35
- slice_string = [slice_string, args].flatten.join("-")
36
- ui.info("")
37
- ui.warn("Please specify server slices joined by dashes and not separate args:\n\n knife cluster #{sub_command} #{slice_string}\n\n")
38
- end
39
- cluster_name, facet_name, slice_indexes = slice_string.split(/[\s\-]/, 3)
34
+ cluster_name, facet_name, slice_indexes = pick_apart(slice_string, *args)
40
35
  desc = predicate_str(cluster_name, facet_name, slice_indexes)
41
36
  #
42
37
  ui.info("Inventorying servers in #{desc}")
@@ -47,6 +42,22 @@ module Ironfan
47
42
  computers.slice(facet_name, slice_indexes)
48
43
  end
49
44
 
45
+ def all_computers(slice_string, *args)
46
+ cluster_name, facet_name, slice_indexes = pick_apart(slice_string, *args)
47
+ computers = broker.discover! Ironfan.load_cluster(cluster_name)
48
+ ui.info("Loaded information for #{computers.size} computer(s) in cluster #{cluster_name}")
49
+ computers
50
+ end
51
+
52
+ def pick_apart(slice_string, *args)
53
+ if not args.empty?
54
+ slice_string = [slice_string, args].flatten.join("-")
55
+ ui.info("")
56
+ ui.warn("Please specify server slices joined by dashes and not separate args:\n\n knife cluster #{sub_command} #{slice_string}\n\n")
57
+ end
58
+ slice_string.split(/[\s\-]/, 3)
59
+ end
60
+
50
61
  def predicate_str(cluster_name, facet_name, slice_indexes)
51
62
  [ "#{ui.color(cluster_name, :bold)} cluster",
52
63
  (facet_name ? "#{ui.color(facet_name, :bold)} facet" : "#{ui.color("all", :bold)} facets"),
@@ -44,26 +44,44 @@ module Ironfan
44
44
 
45
45
  target = get_relevant_slice(* @name_args)
46
46
 
47
- die("No computers to #{sub_command}, exiting", 1) if target.empty?
47
+ unless target.empty?
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
+ end
58
+
59
+ if healthy? and aggregates? and (aggregates_on_noop? or not target.empty?)
60
+ ui.info "Applying aggregations:"
61
+ all_computers(*@name_args).aggregate
62
+ end
48
63
 
49
- ui.info(["\n",
50
- ui.color("Running #{sub_command}", :cyan),
51
- " on #{target.joined_names}..."].join())
52
- unless config[:yes]
64
+ if target.empty?
65
+ ui.warn("No computers to #{sub_command}")
66
+ else
53
67
  ui.info("")
54
- confirm_execution(target)
68
+ ui.info "Finished! Current state:"
69
+ display(target)
55
70
  end
56
71
  #
57
- perform_execution(target)
58
- ui.info("")
59
- ui.info "Finished! Current state:"
60
- display(target)
61
- #
62
72
  exit_if_unhealthy!
63
73
  end
64
74
 
65
75
  def perform_execution(target)
66
76
  target.send(sub_command)
67
77
  end
78
+
79
+ def aggregates?
80
+ true
81
+ end
82
+
83
+ def aggregates_on_noop?
84
+ false
85
+ end
68
86
  end
69
87
  end
data/lib/ironfan.rb CHANGED
@@ -148,8 +148,8 @@ module Ironfan
148
148
  ui.info(" #{"%-15s" % (name.to_s+":")}\t#{ui.color(desc.to_s, *style)}")
149
149
  end
150
150
 
151
- def self.substep(name, desc)
152
- step(name, " - #{desc}", :gray) if verbosity >= 1
151
+ def self.substep(name, desc, color = :gray)
152
+ step(name, " - #{desc}", color) if (verbosity >= 1 or color != :gray)
153
153
  end
154
154
 
155
155
  def self.verbosity
@@ -293,11 +293,16 @@ module Ironfan
293
293
  end
294
294
 
295
295
  def validate
296
- providers = values.map {|c| c.providers.values}.flatten
297
296
  computers = self
298
-
299
297
  values.each{|c| c.validate }
300
- providers.each{|p| p.validate computers }
298
+ values.map {|c| c.providers.values}.flatten.uniq.each {|p| p.validate computers }
299
+ end
300
+
301
+ def aggregate
302
+ computers = self
303
+ provider_keys = values.map {|c| c.chosen_providers({ :providers => :iaas})}.flatten.uniq
304
+ providers = provider_keys.map { |pk| values.map { |c| c.providers[pk] } }.flatten.uniq
305
+ providers.each { |p| p.aggregate! computers }
301
306
  end
302
307
 
303
308
  #
@@ -1,3 +1,5 @@
1
+ require 'digest/md5'
2
+
1
3
  module Ironfan
2
4
  class Dsl
3
5
 
@@ -12,7 +14,9 @@ module Ironfan
12
14
  magic :bootstrap_distro, String, :default => ->{ image_info[:bootstrap_distro] }
13
15
  magic :chef_client_script, String
14
16
  magic :default_availability_zone, String, :default => ->{ availability_zones.first }
17
+ collection :elastic_load_balancers, Ironfan::Dsl::Ec2::ElasticLoadBalancer, :key_method => :name
15
18
  magic :flavor, String, :default => 't1.micro'
19
+ collection :iam_server_certificates, Ironfan::Dsl::Ec2::IamServerCertificate, :key_method => :name
16
20
  magic :image_id, String
17
21
  magic :image_name, String
18
22
  magic :keypair, String
@@ -23,9 +27,9 @@ module Ironfan
23
27
  magic :provider, Whatever, :default => Ironfan::Provider::Ec2
24
28
  magic :public_ip, String
25
29
  magic :region, String, :default => ->{ default_region }
30
+ collection :security_groups, Ironfan::Dsl::Ec2::SecurityGroup, :key_method => :name
26
31
  magic :ssh_user, String, :default => ->{ image_info[:ssh_user] }
27
32
  magic :ssh_identity_dir, String, :default => ->{ Chef::Config.ec2_key_dir }
28
- collection :security_groups, Ironfan::Dsl::Ec2::SecurityGroup, :key_method => :name
29
33
  magic :subnet, String
30
34
  magic :validation_key, String, :default => ->{ IO.read(Chef::Config.validation_key) rescue '' }
31
35
  magic :vpc, String
@@ -63,7 +67,7 @@ module Ironfan
63
67
 
64
68
  def flavor_info
65
69
  if not Chef::Config[:ec2_flavor_info].has_key?(flavor)
66
- ui.warn("Unknown machine image flavor '#{val}'")
70
+ ui.warn("Unknown machine image flavor '#{flavor}'")
67
71
  list_flavors
68
72
  return nil
69
73
  end
@@ -136,6 +140,133 @@ module Ironfan
136
140
  end
137
141
  end
138
142
 
143
+ class ElasticLoadBalancer
144
+
145
+ class HealthCheck < Ironfan::Dsl
146
+ magic :ping_protocol, String, :default => 'HTTP'
147
+ magic :ping_port, Integer, :default => 80
148
+ magic :ping_path, String, :default => '/'
149
+ magic :timeout, Integer, :default => 5
150
+ magic :interval, Integer, :default => 30
151
+ magic :unhealthy_threshold, Integer, :default => 2
152
+ magic :healthy_threshold, Integer, :default => 10
153
+
154
+ def target
155
+ if %w[ HTTP HTTPS ].include?(self.ping_protocol)
156
+ "#{self.ping_protocol}:#{self.ping_port}#{self.ping_path}"
157
+ else
158
+ "#{self.ping_protocol}:#{self.ping_port}"
159
+ end
160
+ end
161
+
162
+ def to_fog
163
+ health_check = {
164
+ 'HealthyThreshold' => healthy_threshold,
165
+ 'Timeout' => timeout,
166
+ 'UnhealthyThreshold' => unhealthy_threshold,
167
+ 'Interval' => interval,
168
+ 'Target' => target
169
+ }
170
+ end
171
+
172
+ end
173
+
174
+ # SSL ciphers susceptible to the BEAST attack
175
+ BEAST_VULNERABLE_CIPHERS = %w[
176
+ Protocol-SSLv2
177
+ ADH-AES128-SHA
178
+ ADH-AES256-SHA
179
+ ADH-CAMELLIA128-SHA
180
+ ADH-CAMELLIA256-SHA
181
+ ADH-DES-CBC-SHA
182
+ ADH-DES-CBC3-SHA
183
+ ADH-RC4-MD5
184
+ ADH-SEED-SHA
185
+ AES128-SHA
186
+ AES256-SHA
187
+ DES-CBC-MD5
188
+ DES-CBC-SHA
189
+ DES-CBC3-MD5
190
+ DES-CBC3-SHA
191
+ DHE-DSS-AES128-SHA
192
+ DHE-DSS-AES256-SHA
193
+ DHE-RSA-AES128-SHA
194
+ DHE-RSA-AES256-SHA
195
+ EDH-DSS-DES-CBC-SHA
196
+ EDH-DSS-DES-CBC3-SHA
197
+ EDH-RSA-DES-CBC-SHA
198
+ EDH-RSA-DES-CBC3-SHA
199
+ EXP-ADH-DES-CBC-SHA
200
+ EXP-ADH-RC4-MD5
201
+ EXP-DES-CBC-SHA
202
+ EXP-EDH-DSS-DES-CBC-SHA
203
+ EXP-EDH-RSA-DES-CBC-SHA
204
+ EXP-KRB5-DES-CBC-MD5
205
+ EXP-KRB5-DES-CBC-SHA
206
+ EXP-KRB5-RC2-CBC-MD5
207
+ EXP-KRB5-RC2-CBC-SHA
208
+ EXP-RC2-CBC-MD5
209
+ IDEA-CBC-SHA
210
+ KRB5-DES-CBC-MD5
211
+ KRB5-DES-CBC-SHA
212
+ KRB5-DES-CBC3-MD5
213
+ KRB5-DES-CBC3-SHA
214
+ PSK-3DES-EDE-CBC-SHA
215
+ PSK-AES128-CBC-SHA
216
+ PSK-AES256-CBC-SHA
217
+ RC2-CBC-MD5
218
+ ]
219
+
220
+ field :name, String
221
+ field :port_mappings, Array, :default => []
222
+ magic :disallowed_ciphers, Array, :default => BEAST_VULNERABLE_CIPHERS
223
+ member :health_check, HealthCheck
224
+
225
+ def map_port(load_balancer_protocol = 'HTTP', load_balancer_port = 80, internal_protocol = 'HTTP', internal_port = 80, iam_server_certificate = nil)
226
+ port_mappings << [ load_balancer_protocol, load_balancer_port, internal_protocol, internal_port, iam_server_certificate ]
227
+ port_mappings.compact!
228
+ port_mappings.uniq!
229
+ end
230
+
231
+ def ssl_policy_to_fog
232
+ result = Hash[ *disallowed_ciphers.collect { |c| [ c, false ] }.flatten ]
233
+ return {
234
+ :name => Digest::MD5.hexdigest("#{disallowed_ciphers.sort.join('')}"),
235
+ :attributes => result,
236
+ }
237
+ end
238
+
239
+ def listeners_to_fog(cert_lookup)
240
+ port_mappings.map do |pm|
241
+ result = {
242
+ 'Protocol' => pm[0], # load_balancer_protocl
243
+ 'LoadBalancerPort' => pm[1], # load_balancer_port
244
+ 'InstanceProtocol' => pm[2], # internal_protocol
245
+ 'InstancePort' => pm[3], # internal_port
246
+ }
247
+ result['SSLCertificateId'] = cert_lookup[pm[4]] if pm[4]
248
+ result
249
+ end
250
+ end
251
+
252
+ end
253
+
254
+ # Although it is unreasonable to talk about an IAM Server Certificate object
255
+ # without a certificate or private_key field value, we also allow users to
256
+ # simply specify the arn of an existing IAM Server Certificate so that their
257
+ # web server certificate is not required to be present in their recipes repo,
258
+ # for security purposes. In that case the :arn field would be non-nil while
259
+ # the :certificate and :private_key fields would be nil, which hints to the
260
+ # EC2 Provider code that it should attempt to validate the existence of the
261
+ # IAM Server Certificate in the EC2 cloud rather than trying to create it.
262
+ class IamServerCertificate < Ironfan::Dsl
263
+ field :name, String
264
+ magic :arn, String, :default => nil
265
+ magic :certificate, String, :default => nil # Actually a PEM encoding
266
+ magic :private_key, String, :default => nil # Actually a PEM encoding
267
+ magic :certificate_chain, String, :default => nil # Actually a PEM encoding
268
+ end
269
+
139
270
  end
140
271
 
141
272
  end