@@ -27,17 +27,6 @@ class Nodes < Application
2727 before :authenticate_every
2828 before :admin_or_requesting_node , :only => [ :update , :destroy , :cookbooks ]
2929
30- CMP = {
31- "<<" => lambda { |v , r | v < r } ,
32- "<=" => lambda { |v , r | v <= r } ,
33- "=" => lambda { |v , r | v == r } ,
34- ">=" => lambda { |v , r | v >= r } ,
35- ">>" => lambda { |v , r | v > r }
36- }
37-
38- qcmp = CMP . keys . map { |k | Regexp . quote k } . join "|"
39- PATTERN = /\A \s *(#{ qcmp } )?\s *(#{ Chef ::Cookbook ::Metadata ::Version ::PATTERN } )\s *\z /
40-
4130 def index
4231 @node_list = Chef ::Node . cdb_list
4332 display ( @node_list . inject ( { } ) do |r , n |
@@ -103,52 +92,12 @@ def cookbooks
10392
10493 private
10594
106- def satisfy ( cookbook , req = nil )
107- r = req . to_s
108- begin
109- versions = Chef ::CookbookVersion . cdb_by_name ( cookbook ) [ cookbook ] . sort
110- rescue NoMethodError
111- raise PreconditionFailed , "cookbook #{ cookbook } does not exist"
112- end
113- if r . nil? or r . empty? or r == "latest"
114- versions
115- elsif r =~ PATTERN
116- comp = $1 || "="
117- ver = Chef ::Cookbook ::Metadata ::Version . new $2
118- versions . select { |v | CMP [ comp ] . call Chef ::Cookbook ::Metadata ::Version . new ( v ) , ver }
119- else
120- raise ArgumentError , "Unrecognized dependency specification: #{ r } "
121- end
122- end
123-
124- def satisfy_all ( cookbook , reqs = [ ] )
125- vers = Array . new
126-
127- if reqs . empty?
128- return Chef ::CookbookVersion . cdb_load ( cookbook , satisfy ( cookbook ) . last )
129- end
130-
131- reqs . each do |pat |
132- v = satisfy ( cookbook , pat )
133- raise ArgumentError , "Can't satisfy dependency #{ pat } for cookbook #{ cookbook } " if v . empty?
134- if vers . empty?
135- vers = v
136- else
137- vers = vers & v
138- raise ArgumentError , "Conflicting dependencies for #{ cookbook } " if vers . empty?
139- end
140- end
141- Chef ::CookbookVersion . cdb_load ( cookbook , vers . last )
142- end
143-
14495 def load_all_files
14596 all_cookbooks = Chef ::Environment . cdb_load_filtered_cookbook_versions ( @node . chef_environment )
14697
14798 included_cookbooks = cookbooks_for_node ( all_cookbooks )
14899 nodes_cookbooks = Hash . new
149- included_cookbooks . each_pair do |cookbook_name , versions |
150- cookbook = satisfy_all ( cookbook_name , versions )
151-
100+ included_cookbooks . each do |cookbook_name , cookbook |
152101 nodes_cookbooks [ cookbook_name . to_s ] = cookbook . generate_manifest_with_urls { |opts | absolute_url ( :cookbook_file , opts ) }
153102 end
154103
@@ -158,12 +107,17 @@ def load_all_files
158107 # returns name -> CookbookVersion for all cookbooks included on the given node.
159108 def cookbooks_for_node ( all_cookbooks )
160109 # expand returns a RunListExpansion which contains recipes, default and override attrs [cb]
161- items = @node . run_list . expand ( 'couchdb' ) . run_list_items
110+ # TODO: check for this on the client side before we make the http request [stephen 9/1/10]
111+ begin
112+ recipes = @node . run_list . expand ( 'couchdb' ) . recipes . with_versions
113+ rescue Chef ::Exceptions ::RecipeVersionConflict => e
114+ raise PreconditionFailed , "#Conflict: #{ e . message } "
115+ end
162116
163117 # TODO: make cookbook loading respect environment's versions [stephen 8/25/10]
164118 # walk run list and accumulate included dependencies
165- items . select { | i | i . recipe? } . inject ( { } ) do |included_cookbooks , rli |
166- expand_cookbook_deps ( included_cookbooks , rli )
119+ recipes . inject ( { } ) do |included_cookbooks , recipe |
120+ expand_cookbook_deps ( included_cookbooks , all_cookbooks , recipe , "Run list" )
167121 included_cookbooks
168122 end
169123 end
@@ -172,47 +126,35 @@ def cookbooks_for_node(all_cookbooks)
172126 # included_cookbooks == hash of name -> CookbookVersion, which is used for returning
173127 # result as well as for tracking which cookbooks we've already
174128 # recursed into
175- # all_cookbooks == hash of name -> CookbookVersion, all cookbooks available
176- # run_list_items == name of cookbook to include
177- def expand_cookbook_deps ( included_cookbooks , run_list_item , constraints = nil )
178- # determine the run list item's parent cookbook, which might be run_list_item in the default case
179- version = "latest"
180-
181- # Fortunately or otherwise we need to deal with both RunListItem objects
182- # and bare strings. strings will come to us via the dependency solver, and
183- # I don't think it's valuable to bash them into an RLI just to avoid this.
184- if run_list_item . kind_of? Chef ::RunList ::RunListItem
185- cookbook_name = ( run_list_item . name [ /^(.+)::/ , 1 ] || run_list_item . name )
186- version = run_list_item . version unless run_list_item . version . nil?
129+ # all_cookbooks == hash of name -> [ CookbookVersion ... ] , all cookbooks available, sorted by version number
130+ # recipe == hash of :name => recipe_name, :version => recipe_version to include
131+ # parent_name == the name of the parent cookbook (or run_list), for reporting broken dependencies
132+ def expand_cookbook_deps ( included_cookbooks , all_cookbooks , recipe , parent_name )
133+ # determine the recipe's parent cookbook, which might be the recipe name in the default case
134+ cookbook_name = ( recipe [ :name ] [ /^(.+)::/ , 1 ] || recipe [ :name ] )
135+ version = recipe [ :version ] ? Gem ::Version . new ( recipe [ :version ] ) : nil
136+ Chef ::Log . debug "Node requires #{ cookbook_name } at version #{ version . to_s } "
137+
138+ # detect the correct cookbook version from the list of available cookbook versions
139+ cookbook = version ? all_cookbooks [ cookbook_name ] . detect { |cb | Gem ::Version . new ( cb . version ) == version } : all_cookbooks [ cookbook_name ] . last
140+ raise PreconditionFailed , "#{ parent_name } depends on cookbook #{ cookbook_name } #{ version . to_s } , which is not available to this node" unless cookbook
141+
142+ # we can't load more than one version of the same cookbook
143+ if included_cookbooks [ cookbook_name ]
144+ a = Gem ::Version . new ( included_cookbooks [ cookbook_name ] . version )
145+ b = Gem ::Version . new ( cookbook . version )
146+ raise PreconditionFailed , "Conflict: Node requires cookbook #{ cookbook_name } at versions #{ a . to_s } and #{ b . to_s } " if a != b
187147 else
188- cookbook_name = ( run_list_item [ /^(.+)::/ , 1 ] || run_list_item . to_s )
189- end
190-
191- Chef ::Log . debug ( "Node requires #{ cookbook_name } at #{ version } " )
192- Chef ::Log . debug ( "Requirement constrained by #{ constraints } " ) if constraints
193-
194- constraints ||= version
195-
196- # include its dependencies
197- included_cookbooks [ cookbook_name ] ||= Array . new
198- included_cookbooks [ cookbook_name ] << constraints
199-
200- cb = satisfy_all ( cookbook_name , constraints )
201-
202- if !cb
203- return false
204- # NOTE [dan/cw] We don't think changing this to an exception breaks stuff.
205- # Chef::Log.warn "#{__FILE__}:#{__LINE__}: in expand_cookbook_deps, cookbook/role #{cookbook_name} could not be found, ignoring it in cookbook expansion"
206- # return included_cookbooks
148+ included_cookbooks [ cookbook_name ] = cookbook
207149 end
208150
209- cb . metadata . dependencies . each do |depname , dep_version_constraints |
210- unless expand_cookbook_deps ( included_cookbooks , depname , dep_version_constraints )
211- raise PreconditionFailed , "cookbook #{ cookbook_name } depends on cookbook #{ depname } , but #{ depname } does not exist"
212-
213- end
151+ # TODO:
152+ # In the past, we have ignored the version constraints from dependency metadata.
153+ # We will continue to do so for the time being, until the Gem::Version
154+ # sytax for the environments feature is replaced with something more permanent
155+ # [stephen 9/1/10]
156+ cookbook . metadata . dependencies . each do |dependency_name , dependency_version_constraints |
157+ expand_cookbook_deps ( included_cookbooks , all_cookbooks , dependency_name , "Cookbook #{ cookbook_name } " )
214158 end
215- true
216159 end
217-
218160end
0 commit comments