cluster_chef 3.0.12 → 3.0.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,65 +0,0 @@
1
- ClusterChef.cluster 'webserver_demo' do
2
- cloud :ec2 do
3
- defaults
4
- availability_zones ['us-east-1d']
5
- flavor 't1.micro' # change to something larger for serious use
6
- backing 'ebs'
7
- image_name 'natty'
8
- bootstrap_distro 'ubuntu10.04-cluster_chef'
9
- chef_client_script 'client.rb'
10
- mount_ephemerals(:tags => { :scratch_dirs => true })
11
- end
12
-
13
- role "nfs_client"
14
- recipe "package_set"
15
-
16
- facet :webnode do
17
- instances 6
18
- role "nginx"
19
- role "redis_client"
20
- role "mysql_client"
21
- role "elasticsearch_client"
22
- role "awesome_website"
23
- role "web_server" # this triggers opening appropriate ports
24
- # Rotate nodes among availability zones
25
- azs = ['us-east-1d', 'us-east-1b', 'us-east-1c']
26
- (0...instances).each do |idx|
27
- server(idx).cloud.availability_zones [azs[ idx % azs.length ]]
28
- end
29
- # Rote nodes among A/B testing groups
30
- (0..instances).each do |idx|
31
- server(idx).chef_node.normal[:split_testing] = ( (idx % 2 == 0) ? 'A' : 'B' )
32
- end
33
- end
34
-
35
- facet :dbnode do
36
- instances 2
37
- role "mysql_server"
38
- role "redis_client"
39
- # burly master, wussier slaves
40
- cloud.flavor "m1.large"
41
- server(0) do
42
- cloud.flavor "c1.xlarge"
43
- end
44
-
45
- volume(:data) do
46
- size 50
47
- keep true
48
- device '/dev/sdi'
49
- mount_point '/data/db'
50
- mount_options 'defaults,nouuid,noatime'
51
- fstype 'xfs'
52
- snapshot_id 'snap-d9c1edb1'
53
- end
54
- end
55
-
56
- facet :esnode do
57
- instances 1
58
- role "nginx"
59
- role "redis_server"
60
- role "elasticsearch_data_esnode"
61
- role "elasticsearch_http_esnode"
62
- #
63
- cloud.flavor "m1.large"
64
- end
65
- end
@@ -1,588 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- #
4
- # cookbook_munger.rb -- keep cookbook metadata complete, consistent and correct.
5
- #
6
- # This script reads the actual content of a cookbook -- actually interpreting
7
- # the metadata.rb and attribute files, along with recipes/resources/etc files'
8
- # headers -- and re-generates the metadata.rb and README files.
9
- #
10
- # It also has hooks to do a limited amount of validation and linting.
11
- #
12
-
13
- require 'erubis'
14
- require 'chef/mash'
15
- require 'chef/mixin/from_file'
16
-
17
- require 'configliere'
18
- require 'gorillib/metaprogramming/class_attribute'
19
- require 'gorillib/hash/reverse_merge'
20
- require 'gorillib/object/blank'
21
- require 'gorillib/hash/compact'
22
- require 'gorillib/string/inflections'
23
- require 'gorillib/string/human'
24
- require 'gorillib/logger/log'
25
- require 'set'
26
-
27
- $:.unshift File.expand_path('..', File.dirname(__FILE__))
28
- require 'cluster_chef/dsl_object'
29
-
30
- # silence the chef log
31
- class Chef ; class Log ; def self.info(*args) ; end ; def self.debug(*args) ; end ; end ; end
32
-
33
- Settings.define :maintainer, :default => 'default mantainer name', :default => "Philip (flip) Kromer - Infochimps, Inc"
34
- Settings.define :maintainer_email, :default => 'default email to add to cookbook headers', :default => "coders@infochimps.com"
35
- Settings.define :license, :default => 'default license to apply to cookbooks', :default => "Apache 2.0"
36
- #
37
- Settings.define :cookbook_paths, :description => 'list of paths holding cookbooks', :type => Array, :default => ["./{site-cookbooks,meta-cookbooks}"]
38
- #
39
- Settings.use(:commandline)
40
- Settings.resolve!
41
-
42
- String.class_eval do
43
- def commentize
44
- self.gsub(/\n/, "\n# ").gsub(/\n# \n/, "\n#\n")
45
- end
46
- end
47
-
48
- module CookbookMunger
49
- TEMPLATE_ROOT = File.expand_path('cookbook_munger', File.dirname(__FILE__))
50
-
51
- # ===========================================================================
52
- #
53
- # DummyAttribute -- holds metadata about a single cookbook attribute.
54
- #
55
- # named like a path: node[:pig][:home_dir] is 'pig/home_dir'
56
- #
57
- class DummyAttribute
58
- attr_accessor :name
59
- attr_accessor :display_name
60
- attr_accessor :description
61
- attr_accessor :choice
62
- attr_accessor :calculated
63
- attr_accessor :type
64
- attr_accessor :required
65
- attr_accessor :recipes
66
- attr_accessor :default
67
-
68
- def initialize(name, hsh={})
69
- self.name = name
70
- merge!(hsh)
71
- @display_name ||= ''
72
- end
73
-
74
- def merge!(hsh)
75
- hsh.each do |key, val|
76
- self.send("#{key}=", val) unless val.blank?
77
- end
78
- end
79
-
80
- def inspect
81
- "attr[#{name}:#{default.inspect}]"
82
- end
83
-
84
- def bracketed_name
85
- name.split("/").map{|s| "[:#{s}]" }.join
86
- end
87
-
88
- def keys
89
- [:display_name, :description, :choice, :calculated, :type, :required, :recipes, :default]
90
- end
91
-
92
- def to_hash
93
- hsh = {}
94
- self.description = display_name if description.blank?
95
- keys.each do |key|
96
- hsh[key] = self.send(key) if instance_variable_defined?("@#{key}")
97
- end
98
- case hsh[:default]
99
- when Symbol, Numeric, TrueClass, NilClass, FalseClass then hsh[:default] = hsh[:default].to_s
100
- when Hash then hsh[:type] ||= 'hash'
101
- when Array then hsh[:type] ||= 'array'
102
- end
103
- hsh
104
- end
105
-
106
- def pretty_str
107
- str = [ %Q{attribute "#{name}"} ]
108
- to_hash.each do |key, val|
109
- str << (" :%-21s => %s" % [ key, val.inspect ])
110
- end
111
- str.flatten.join(",\n")
112
- end
113
-
114
- end
115
-
116
- # ===========================================================================
117
- #
118
- # DummyAttributeCollection -- the cascading buckets to hold attributes
119
- #
120
- # This auto-vivifies: just saying `foo[:bar][:baz][:bing]` results in
121
- # foo becoming
122
- # `{ :bar => { :baz => { :bing => {} } } }`
123
- #
124
- class DummyAttributeCollection < Mash
125
- attr_accessor :path
126
-
127
- def initialize(path='')
128
- self.path = path
129
- super(){|hsh,key| hsh[key] = DummyAttributeCollection.new(sub_path(key)) }
130
- end
131
-
132
- def setter(key=nil)
133
- # key ? (self[key] = DummyAttributeCollection.new(sub_path(key))) : self
134
- self
135
- end
136
-
137
- def sub_path(key)
138
- path.blank? ? key.to_s : "#{path}/#{key}"
139
- end
140
-
141
- def []=(key, val)
142
- unless val.is_a?(DummyAttributeCollection) || val.is_a?(DummyAttribute)
143
- val = DummyAttribute.new(sub_path(key), :default =>val)
144
- end
145
- super(key, val)
146
- end
147
-
148
- def attrs
149
- [ leafs.values, branches.map{|key,val| val.attrs } ].flatten
150
- end
151
-
152
- def leafs
153
- select{|key,val| not val.is_a?(DummyAttributeCollection) }
154
- end
155
- def branches
156
- select{|key,val| val.is_a?(DummyAttributeCollection) }
157
- end
158
-
159
- def pretty_str
160
- str = []
161
- attrs.each{|attrib| str << attrib.pretty_str }
162
- str.join("\n\n")
163
- end
164
-
165
- end
166
-
167
- # ===========================================================================
168
- #
169
- # CookbookComponent - shared mixin methods for Chef-DSL files (recipes,
170
- # attributes, definitions, resources, etc)
171
- #
172
- module CookbookComponent
173
- attr_reader :name, :desc, :filename
174
- # the cookbook object this belongs to
175
- attr_reader :cookbook
176
- attr_accessor :header_lines, :body_lines
177
-
178
- def initialize(cookbook, name, desc, filename, *args, &block)
179
- super(*args, &block)
180
- @cookbook = cookbook
181
- @name = name
182
- @desc = desc
183
- @filename = filename
184
- end
185
-
186
- def raw_lines
187
- begin
188
- @raw_lines ||= File.readlines(filename).map(&:chomp)
189
- rescue Errno::ENOENT => boom
190
- warn boom.to_s
191
- @raw_lines ||= []
192
- end
193
- end
194
-
195
- def read
196
- @header_lines = []
197
- @body_lines = []
198
- # Gobble the header -- all comment lines following the first
199
- until raw_lines.first !~ /^#/ || raw_lines.empty?
200
- line = raw_lines.first
201
- header_lines << raw_lines.shift
202
- process_header_line(line)
203
- end
204
- # skip blank lines that follow the header
205
- until raw_lines.first =~ /\S+/ || raw_lines.empty?
206
- raw_lines.shift
207
- end
208
- raw_lines.each do |line|
209
- body_lines << line
210
- process_body_line(line)
211
- end
212
- end
213
-
214
- # called on each header line in #read
215
- def process_header_line(line)
216
- # override in subclass if you like
217
- end
218
- # called on each body line in #read
219
- def process_body_line(line)
220
- # override in subclass if you like
221
- end
222
-
223
- # save to {filename}.bak
224
- def dump
225
- File.open(filename+'.bak', 'w') do |f|
226
- f << header_lines.join("\n")
227
- f << "\n\n"
228
- f << body_lines.join("\n")
229
- f << "\n"
230
- end
231
- end
232
-
233
- # Use the chef from_file mixin -- instance_exec the file
234
- def execute!
235
- from_file(filename)
236
- end
237
-
238
- module ClassMethods
239
- def read(cookbook, name, desc, filename)
240
- attr_file = self.new(cookbook, name, desc, filename)
241
- attr_file.read
242
- attr_file
243
- end
244
- end
245
- def self.included(base) base.extend ClassMethods ; end
246
- end
247
-
248
- # ===========================================================================
249
- #
250
- # RecipeFile -- a chef recipe
251
- #
252
- class RecipeFile
253
- attr_accessor :copyright_lines, :author_lines, :include_recipes, :include_cookbooks
254
- include CookbookComponent
255
-
256
- def initialize(*args, &block)
257
- super
258
- @include_recipes = []
259
- @include_cookbooks = []
260
- end
261
-
262
- def process_header_line(line)
263
- self.author_lines << "# Author:: #{$1}" if line =~ /^# Author::\s*(.*)/
264
- self.copyright_lines << line if line =~ /^# Copyright / && line !~ /YOUR_COMPANY_NAME/
265
- end
266
-
267
- def process_body_line(line)
268
- if line =~ /include_recipe\(?\s*[\"\']([^\"\'\:]*?)(::.*?)?[\"\']\s*\)?(?:[#;].*)?$/
269
- i_cb, i_rp = [$1, $2]
270
- i_rp = nil if i_rp == "default"
271
- self.include_cookbooks << i_cb
272
- self.include_recipes << [i_cb, i_rp].compact.join("::")
273
- end
274
- end
275
-
276
- def read
277
- self.author_lines = []
278
- self.copyright_lines = []
279
- super
280
- self.copyright_lines = ["# Copyright #{cookbook.copyright_text}"] if copyright_lines.blank?
281
- self.author_lines = ["# Author:: #{cookbook.maintainer}"] if author_lines.blank?
282
- end
283
-
284
- def dump
285
- super
286
- end
287
-
288
- def lint
289
- if self.name == 'default'
290
- sketchy = (include_recipes & %w[ runit::default java::sun ])
291
- if sketchy.present? then warn "Recipe #{cookbook.name}::#{name} includes #{sketchy.inspect} -- put these in component cookbooks, not the default." ; end
292
- end
293
- end
294
-
295
- def generate_header!
296
- new_header_lines = ['#']
297
- new_header_lines << "# Cookbook Name:: #{cookbook.name}"
298
- new_header_lines << "# Description:: #{desc.commentize}"
299
- new_header_lines << "# Recipe:: #{name}"
300
- new_header_lines += author_lines
301
- new_header_lines << "#"
302
- new_header_lines += copyright_lines
303
- new_header_lines << "#"
304
- new_header_lines << ("# "+cookbook.short_license_text.commentize) << '#'
305
- self.header_lines = new_header_lines
306
- end
307
-
308
- end
309
-
310
- # ===========================================================================
311
- #
312
- # AttributeFile -- a chef attribute file
313
- #
314
- # The metadata in here will be merged with anything found in the metadata.rb
315
- # file, with these winning
316
- #
317
- class AttributeFile
318
- include Chef::Mixin::FromFile
319
- include CookbookComponent
320
- attr_reader :all_attributes
321
-
322
- def initialize(*args, &block)
323
- super(*args, &block)
324
- @all_attributes = DummyAttributeCollection.new
325
- end
326
-
327
- #
328
- # Fake the DSL so we can run the attributes file in our context
329
- #
330
-
331
- def default
332
- all_attributes
333
- end
334
- def set
335
- all_attributes
336
- end
337
- def attribute?(key) node.has_key?(key.to_sym) ; end
338
- def node
339
- {
340
- :hostname => 'hostname',
341
- :cluster_name => :cluster_name,
342
- :platform => 'ubuntu', :platform_version => '10.4',
343
- :cloud => { :private_ips => ['10.20.30.40'] },
344
- :cpu => { :total => 2 }, :memory => { :total => 2 },
345
- :kernel => { :os => '', :release => '', :machine => '' ,},
346
- :ec2 => { :instance_type => 'm1.large', },
347
- :hbase => { :home_dir => '/usr/lib/hbase', },
348
- :zookeeper => { :home_dir => '/usr/lib/zookeeper', },
349
- :redis => { :slave => 'no' },
350
- :ipaddress => '10.20.30.40',
351
- :languages => { :ruby => { :version => "1.9" } },
352
- :cassandra => { :mx4j_version => 'x.x' },
353
- :ganglia => { :home_dir => '/var/lib/ganglia' },
354
- }.merge(@all_attributes)
355
- end
356
- def method_missing(meth, *args)
357
- if args.empty? && node.has_key?(meth)
358
- node[meth]
359
- else
360
- super(meth, *args)
361
- end
362
- end
363
-
364
- def value_for_platform(hsh)
365
- hsh["default"] || hsh[hsh.keys.first]
366
- end
367
-
368
- end
369
-
370
- # ===========================================================================
371
- #
372
- # CookbookMetadata -- the main deal. Unifies information from metadata.rb, the
373
- # attributes/ files, the rest of the tree; produces a synthesized metadata.rb
374
- # and README.md.
375
- #
376
- class CookbookMetadata < ClusterChef::DslObject
377
- include Chef::Mixin::FromFile
378
- attr_reader :dirname
379
- has_keys :name, :author, :maintainer, :maintainer_email, :license, :version, :description, :long_desc_gen
380
- has_keys :long_description
381
- attr_reader :all_depends, :all_recipes, :all_attributes, :all_resources, :all_supports, :all_recommends
382
- attr_reader :components, :attribute_files
383
-
384
- # also: grouping, conflicts, provides, replaces, recommends, suggests
385
-
386
- # definition: provides "here(:kitty, :time_to_eat)"
387
- # resource: provides "service[snuggle]"
388
-
389
- def initialize(nm, dirname, *args, &block)
390
- super(*args, &block)
391
- name(nm)
392
- @dirname = dirname
393
- @attribute_files = {}
394
- @all_attributes = CookbookMunger::DummyAttributeCollection.new
395
- @all_depends ||= {}
396
- @all_recommends ||= {}
397
- @all_supports ||= %w[ debian ubuntu ]
398
- @all_recipes ||= {}
399
- @all_resources ||= {}
400
- long_desc_gen(%Q{IO.read(File.join(File.dirname(__FILE__), 'README.md'))}) unless long_desc_gen
401
- end
402
-
403
- #
404
- # Fake DSL
405
- #
406
-
407
- # add dependency to list
408
- def depends(nm, ver=nil) @all_depends[nm] = (ver ? %Q{"#{nm}", "#{ver}"} : %Q{"#{nm}"} ) ; end
409
- # add recommended dependency to list
410
- def recommends(nm, ver=nil) @all_recommends[nm] = (ver ? %Q{"#{nm}", "#{ver}"} : %Q{"#{nm}"} ) ; end
411
- # add supported OS to list
412
- def supports(nm, ver=nil) @all_supports << nm ; @all_supports.uniq! ; @all_supports ; end
413
- # add resource to list
414
- def resource(nm, desc) @all_resources[nm] = { :name => nm, :description => desc } ; end
415
-
416
- # pull out the non-generated part of the README
417
- def long_description(val=nil)
418
- return @long_description.to_s if val.nil?
419
- lines = val.split(/\n/)
420
- until (not lines.last.blank?) || lines.empty? ; lines.pop ; end
421
- if lines.last =~ /^> readme generated by \[cluster_chef\]/
422
- # it's one of ours; strip out the generated material
423
- until (lines.first =~ /^## (Overview|Attributes)/) || lines.empty?
424
- lines.shift
425
- end
426
- desc = []
427
- lines.shift if lines.first =~ /^## (Overview)/
428
- until (lines.first =~ /^## Attributes/) || lines.empty?
429
- desc << lines.shift
430
- end
431
- else
432
- desc = lines
433
- end
434
- @long_description = desc.join("\n").strip
435
- end
436
-
437
-
438
- # add attribute to list
439
- def attribute(nm, info={})
440
- return if info[:type] == 'hash'
441
- path_segs = nm.split("/")
442
- leaf = path_segs.pop
443
- attr_branch = @all_attributes
444
- path_segs.each{|seg| attr_branch = attr_branch[seg] }
445
- if info.present? || (not attr_branch.has_key?(leaf))
446
- attr_branch[leaf] = CookbookMunger::DummyAttribute.new(nm, info)
447
- end
448
- attr_branch[leaf]
449
- end
450
-
451
- # add recipe to list
452
- def recipe(recipe_name, desc=nil)
453
- recipe_name = 'default' if recipe_name == name
454
- recipe_name = recipe_name.gsub(/^#{name}::/, "")
455
- #
456
- desc = (recipe_name == 'default' ? "Base configuration for #{name}" : recipe_name.titleize) if (desc.blank? || desc == recipe_name.titleize)
457
- filename = file_in_cookbook("recipes/#{recipe_name}.rb")
458
- @all_recipes[recipe_name] ||= RecipeFile.read(self, recipe_name, desc, filename)
459
- @all_recipes[recipe_name].desc ||= desc if desc.present?
460
- @all_recipes[recipe_name]
461
- end
462
-
463
- #
464
- # Read project
465
- #
466
-
467
- def file_in_cookbook(filename)
468
- File.expand_path(filename, self.dirname)
469
- end
470
-
471
- def load_components
472
- from_file(file_in_cookbook("metadata.rb"))
473
-
474
- @components = {
475
- :attributes => Dir[file_in_cookbook('attributes/*.rb') ].map{|f| nm = File.basename(f, '.rb') ; AttributeFile.read(self, nm, "attributes[#{self.name}::#{nm}", f) },
476
- :recipes => Dir[file_in_cookbook('recipes/*.rb') ].map{|f| nm = File.basename(f, '.rb') ; recipe("#{name}::#{nm}") },
477
- :resources => Dir[file_in_cookbook('resources/*.rb') ].map{|f| File.basename(f, '.rb') },
478
- :providers => Dir[file_in_cookbook('providers/*.rb') ].map{|f| File.basename(f, '.rb') },
479
- :templates => Dir[file_in_cookbook('templates/**/*.rb') ].map{|f| File.join(File.basename(File.dirname(f)), File.basename(f, '.rb')) },
480
- :definitions => Dir[file_in_cookbook('definitions/*.rb') ].map{|f| File.basename(f, '.rb') },
481
- :libraries => Dir[file_in_cookbook('definitions/*.rb') ].map{|f| File.basename(f, '.rb') },
482
- }
483
-
484
- components[:attributes].each do |attrib_file|
485
- merge_attribute_file(attrib_file)
486
- end
487
- end
488
-
489
- def merge_attribute_file(attrib_file)
490
- attrib_file.execute!
491
- attrib_file.all_attributes.attrs.each do |af_attrib|
492
- my_attrib = attribute(af_attrib.name)
493
- my_attrib.merge!(af_attrib.to_hash)
494
- end
495
- end
496
-
497
- def lint!
498
- # Settings.each do |attr, sval|
499
- # my_val = self.send(attr) rescue nil
500
- # warn([name, attr, sval, my_val ]) unless sval == my_val
501
- # end
502
- lint_dependencies
503
- lint_presence
504
- components[:recipes].each(&:lint)
505
- end
506
-
507
- def lint_dependencies
508
- include_cookbooks = []
509
- components[:recipes].each do |recipe|
510
- include_cookbooks += recipe.include_cookbooks
511
- end
512
- include_cookbooks = include_cookbooks.sort.uniq
513
- missing_dependencies = (include_cookbooks - all_depends.keys - [name])
514
- missing_includes = (all_depends.keys - include_cookbooks - [name])
515
- warn "Coookbook #{name} doesn't declare dependency on #{missing_dependencies.join(", ")}, but has an include_recipe that refers to it" if missing_dependencies.present?
516
- warn "Coookbook #{name} declares dependency on #{missing_includes.join(", ")}, but never calls include_recipe with it" if missing_includes.present?
517
- end
518
-
519
- def lint_presence
520
- components[:recipes].each do |recipe|
521
- warn "Recipe #{name}::#{recipe.name} #{recipe.filename} missing, though it is alluded to in #{name}/metadata.rb" unless File.exists?(recipe.filename)
522
- end
523
- end
524
-
525
- def dump
526
- load_components
527
- lint!
528
- File.open(file_in_cookbook('metadata.rb.bak'), 'w') do |f|
529
- f << render('metadata.rb')
530
- end
531
- File.open(file_in_cookbook('README.md.bak'), 'w') do |f|
532
- f << render('README.md')
533
- end
534
- components[:recipes].each do |recipe|
535
- recipe.generate_header!
536
- recipe.dump
537
- end
538
- end
539
-
540
- #
541
- # Content
542
- #
543
-
544
- def self.licenses
545
- return @licenses if @licenses
546
- @licenses = YAML.load(File.read(File.expand_path("licenses.yaml", CookbookMunger::TEMPLATE_ROOT)))
547
- end
548
-
549
- def license_info
550
- @license_info = self.class.licenses.values.detect{|lic| lic[:name] == license }
551
- end
552
-
553
- def short_license_text
554
- license_info ? license_info[:short] : '(no license specified)'
555
- end
556
-
557
- def copyright_text
558
- "2011, #{maintainer}"
559
- end
560
-
561
- #
562
- # Display
563
- #
564
-
565
- def render(filename)
566
- self.class.template(filename).result(self.send(:binding))
567
- end
568
-
569
- def self.template(filename)
570
- template_text = File.read(File.expand_path("#{filename}.erb", CookbookMunger::TEMPLATE_ROOT))
571
- Erubis::Eruby.new(template_text)
572
- end
573
- end
574
-
575
- puts "-----------------------------------------------------------------------"
576
- puts "\n\n++++++++++++++++ COOKBOOK MUNGE NOM NOM NOM +++++++++++++++++++\n\n"
577
- Settings.cookbook_paths.each do |cookbook_path|
578
-
579
- Dir["#{cookbook_path}/*/metadata.rb"].each do |f|
580
- dirname = File.dirname(f)
581
- nm = File.basename(dirname)
582
- puts "====== %-20s ====================" % nm
583
- cookbook_metadata = CookbookMetadata.new(nm, dirname, Settings.dup)
584
- cookbook_metadata.dump
585
- end
586
- end
587
-
588
- end