spiceweasel 1.2.0 → 2.0.0

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.
@@ -0,0 +1,53 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@opscode.com>)
3
+ #
4
+ # Copyright:: 2012, Opscode, Inc <legal@opscode.com>
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
+ module Spiceweasel
20
+ class Clusters
21
+
22
+ attr_reader :create, :delete
23
+
24
+ def initialize(clusters, cookbooks, environments, roles)
25
+ @create = Array.new
26
+ @delete = Array.new
27
+ if clusters
28
+ Spiceweasel::Log.debug("clusters: #{clusters}")
29
+ clusters.each do |cluster|
30
+ cluster_name = cluster.keys.first
31
+ Spiceweasel::Log.debug("cluster: '#{cluster_name}' '#{cluster[cluster_name]}'")
32
+ # add a tag to the Nodes
33
+ cluster[cluster_name].each do |node|
34
+ node_name = node.keys.first
35
+ run_list = node[node_name]['run_list'] || ''
36
+ options = node[node_name]['options'] || ''
37
+ # cluster tag is the cluster name + runlist
38
+ tag = " -j '{\"tags\":[\"#{cluster_name}+#{run_list.gsub(/[ ,\[\]:]/, '')}\"]}'"
39
+ Spiceweasel::Log.debug("cluster: #{cluster_name}:#{node_name}:tag:#{tag}")
40
+ #push the tag back on the options
41
+ node[node_name]['options'] = options + tag
42
+ end
43
+ Spiceweasel::Log.debug("cluster2: '#{cluster_name}' '#{cluster[cluster_name]}'")
44
+ # let's reuse the Nodes logic
45
+ nodes = Spiceweasel::Nodes.new(cluster[cluster_name], cookbooks, environments, roles)
46
+ @create.concat(nodes.create)
47
+ @delete.concat(nodes.delete)
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@opscode.com>)
3
+ #
4
+ # Copyright:: 2012, Opscode, Inc <legal@opscode.com>
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 'mixlib/config'
20
+
21
+ module Spiceweasel
22
+ class Config
23
+ extend Mixlib::Config
24
+
25
+ debug false
26
+
27
+ # logging
28
+ log_level :info
29
+ log_location STDOUT
30
+
31
+ knife_options ''
32
+
33
+ # do we really need these?
34
+ delete false
35
+ execute false
36
+ extractjson false
37
+ extractlocal false
38
+ extractyaml false
39
+ help false
40
+ novalidation false
41
+ parallel false
42
+ rebuild false
43
+ siteinstall false
44
+
45
+ end
46
+ end
@@ -0,0 +1,105 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@opscode.com>)
3
+ #
4
+ # Copyright:: 2011-2012, Opscode, Inc <legal@opscode.com>
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 'chef/cookbook/metadata'
20
+
21
+ module Spiceweasel
22
+ class Cookbooks
23
+
24
+ attr_reader :cookbook_list, :create, :delete
25
+
26
+ def initialize(cookbooks = [])
27
+ @create = Array.new
28
+ @delete = Array.new
29
+ @cookbook_list = Hash.new
30
+ @dependencies = Array.new
31
+ #validate each of the cookbooks specified in the manifest
32
+ if cookbooks
33
+ Spiceweasel::Log.debug("cookbooks: #{cookbooks}")
34
+ cookbooks.each do |cookbook|
35
+ name = cookbook.keys.first
36
+ if cookbook[name]
37
+ version = cookbook[name]['version']
38
+ options = cookbook[name]['options']
39
+ end
40
+ Spiceweasel::Log.debug("cookbook: #{name} #{version} #{options}")
41
+ if File.directory?("cookbooks")
42
+ if File.directory?("cookbooks/#{name}") #TODO use the name from metadata
43
+ validateMetadata(name,version) unless Spiceweasel::Config[:novalidation]
44
+ else
45
+ if Spiceweasel::Config[:siteinstall] #use knife cookbook site install
46
+ @create.push("knife cookbook#{Spiceweasel::Config[:knife_options]} site install #{name} #{version} #{options}")
47
+ else #use knife cookbook site download, untar and then remove the tarball
48
+ @create.push("knife cookbook#{Spiceweasel::Config[:knife_options]} site download #{name} #{version} --file cookbooks/#{name}.tgz #{options}")
49
+ @create.push("tar -C cookbooks/ -xf cookbooks/#{name}.tgz")
50
+ @create.push("rm -f cookbooks/#{name}.tgz")
51
+ end
52
+ end
53
+ elsif !Spiceweasel::Config[:novalidation]
54
+ STDERR.puts "ERROR: 'cookbooks' directory not found, unable to validate, download and load cookbooks"
55
+ exit(-1)
56
+ end
57
+ @create.push("knife cookbook#{Spiceweasel::Config[:knife_options]} upload #{name} #{options}")
58
+ @delete.push("knife cookbook#{Spiceweasel::Config[:knife_options]} delete #{name} #{version} -a -y")
59
+ @cookbook_list[name] = version #used for validation
60
+ end
61
+ validateDependencies() unless Spiceweasel::Config[:novalidation]
62
+ end
63
+ end
64
+
65
+ #check the metadata for versions and gather deps
66
+ def validateMetadata(cookbook,version)
67
+ #check metadata.rb for requested version
68
+ metadata_file = "cookbooks/#{cookbook}/metadata.rb"
69
+ metadata = Chef::Cookbook::Metadata.new
70
+ metadata.from_file(metadata_file)
71
+ Spiceweasel::Log.debug("validateMetadata: #{cookbook} #{metadata.name} #{metadata.version}")
72
+ # Should the cookbook directory match the name in the metadata?
73
+ if metadata.name.empty?
74
+ Spiceweasel::Log.warn("No cookbook name in the #{cookbook} metadata.rb.")
75
+ elsif cookbook != metadata.name
76
+ STDERR.puts "ERROR: Cookbook '#{cookbook}' does not match the name '#{metadata.name}' in #{cookbook}/metadata.rb."
77
+ exit(-1)
78
+ end
79
+ if version && metadata.version != version
80
+ STDERR.puts "ERROR: Invalid version '#{version}' of '#{cookbook}' requested, '#{metadata.version}' is already in the cookbooks directory."
81
+ exit(-1)
82
+ end
83
+ metadata.dependencies.each do |dependency|
84
+ Spiceweasel::Log.debug("cookbook #{cookbook} metadata dependency: #{dependency}")
85
+ @dependencies.push(dependency[0])
86
+ end
87
+ end
88
+
89
+ #compare the list of cookbook deps with those specified
90
+ def validateDependencies()
91
+ Spiceweasel::Log.debug("cookbook validateDependencies: '#{@dependencies}'")
92
+ @dependencies.each do |dep|
93
+ if !member?(dep)
94
+ STDERR.puts "ERROR: Cookbook dependency '#{dep}' is missing from the list of cookbooks in the manifest."
95
+ exit(-1)
96
+ end
97
+ end
98
+ end
99
+
100
+ def member?(cookbook)
101
+ cookbook_list.keys.include?(cookbook)
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,89 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@opscode.com>)
3
+ #
4
+ # Copyright:: 2011-2012, Opscode, Inc <legal@opscode.com>
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 'json'
20
+
21
+ module Spiceweasel
22
+ class DataBags
23
+
24
+ attr_reader :create, :delete
25
+
26
+ def initialize(data_bags = [])
27
+ @create = Array.new
28
+ @delete = Array.new
29
+ if data_bags
30
+ Spiceweasel::Log.debug("data bags: #{data_bags}")
31
+ data_bags.each do |data_bag|
32
+ db = data_bag.keys.first
33
+ #check directories
34
+ if !File.directory?("data_bags") && !Spiceweasel::Config[:novalidation]
35
+ STDERR.puts "ERROR: 'data_bags' directory not found, unable to validate or load data bag items"
36
+ exit(-1)
37
+ end
38
+ if !File.directory?("data_bags/#{db}") && !Spiceweasel::Config[:novalidation]
39
+ STDERR.puts "ERROR: 'data_bags/#{db}' directory not found, unable to validate or load data bag items"
40
+ exit(-1)
41
+ end
42
+ @create.push("knife data bag#{Spiceweasel::Config[:knife_options]} create #{db}")
43
+ @delete.push("knife data bag#{Spiceweasel::Config[:knife_options]} delete #{db} -y")
44
+ if data_bag[db]
45
+ items = data_bag[db]['items']
46
+ secret = data_bag[db]['secret']
47
+ if secret && !File.exists?(File.expand_path(secret)) && !Spiceweasel::Config[:novalidation]
48
+ STDERR.puts "ERROR: secret key #{secret} not found, unable to load encrypted data bags for data bag #{db}."
49
+ exit(-1)
50
+ end
51
+ end
52
+ items = [] if items.nil?
53
+ Spiceweasel::Log.debug("data bag: #{db} #{secret} #{items}")
54
+ items.each do |item|
55
+ Spiceweasel::Log.debug("data bag #{db} item: #{item}")
56
+ if item =~ /\*/ #wildcard support, will fail if directory not present
57
+ files = Dir.glob("data_bags/#{db}/*.json")
58
+ items.concat(files.collect {|x| x[x.rindex('/')+1..-6]})
59
+ Spiceweasel::Log.debug("found items '#{items}' for data bag: #{db}")
60
+ next
61
+ end
62
+ validateItem(db, item) unless Spiceweasel::Config[:novalidation]
63
+ if secret
64
+ @create.push("knife data bag#{Spiceweasel::Config[:knife_options]} from file #{db} #{item}.json --secret-file #{secret}")
65
+ else
66
+ @create.push("knife data bag#{Spiceweasel::Config[:knife_options]} from file #{db} #{item}.json")
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ #validate the item to be loaded
74
+ def validateItem(db, item)
75
+ if !File.exists?("data_bags/#{db}/#{item}.json")
76
+ STDERR.puts "ERROR: data bag '#{db}' item '#{item}' file 'data_bags/#{db}/#{item}.json' does not exist"
77
+ exit(-1)
78
+ end
79
+ f = File.read("data_bags/#{db}/#{item}.json")
80
+ itemfile = JSON.parse(f) #invalid JSON will throw a trace
81
+ #validate the id matches the file name
82
+ if !item.eql?(itemfile['id'])
83
+ STDERR.puts "ERROR: data bag '#{db}' item '#{item}' listed in the manifest does not match the id '#{itemfile['id']}' within the 'data_bags/#{db}/#{item}.json' file."
84
+ exit(-1)
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@opscode.com>)
3
+ #
4
+ # Copyright:: 2011-2012, Opscode, Inc <legal@opscode.com>
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 'json'
20
+
21
+ module Spiceweasel
22
+ class Environments
23
+
24
+ attr_reader :environment_list, :create, :delete
25
+
26
+ def initialize(environments = [], cookbooks = {})
27
+ @create = Array.new
28
+ @delete = Array.new
29
+ @environment_list = Array.new
30
+ if environments
31
+ Spiceweasel::Log.debug("environments: #{environments}")
32
+ environments.each do |env|
33
+ name = env.keys[0]
34
+ Spiceweasel::Log.debug("environment: #{name}")
35
+ if File.directory?("environments")
36
+ validate(name, cookbooks) unless Spiceweasel::Config[:novalidation]
37
+ elsif !Spiceweasel::Config[:novalidation]
38
+ STDERR.puts "'environments' directory not found, unable to validate or load environments"
39
+ exit(-1)
40
+ end
41
+ if File.exists?("environments/#{name}.json")
42
+ @create.push("knife environment#{Spiceweasel::Config[:knife_options]} from file #{name}.json")
43
+ else #assume no .json means they want .rb and catchall for misssing dir
44
+ @create.push("knife environment#{Spiceweasel::Config[:knife_options]} from file #{name}.rb")
45
+ end
46
+ @delete.push("knife environment#{Spiceweasel::Config[:knife_options]} delete #{name} -y")
47
+ @environment_list.push(name)
48
+ end
49
+ end
50
+ end
51
+
52
+ #validate the content of the environment file
53
+ def validate(environment, cookbooks)
54
+ #validate the environments passed in match the name of either the .rb or .json
55
+ if File.exists?("environments/#{environment}.rb")
56
+ #validate that the name inside the file matches
57
+ name = File.open("environments/#{environment}.rb").grep(/^name/)[0].split()[1].gsub(/"/,'').to_s
58
+ if !environment.eql?(name)
59
+ STDERR.puts "ERROR: Environment '#{environment}' listed in the manifest does not match the name '#{name}' within the environments/#{environment}.rb file."
60
+ exit(-1)
61
+ end
62
+ #validate the cookbooks exist if they're mentioned
63
+ envcookbooks = File.open("environments/#{environment}.rb").grep(/^cookbook /)
64
+ envcookbooks.each do |cb|
65
+ dep = cb.split()[1].gsub(/"/,'').gsub(/,/,'')
66
+ Spiceweasel::Log.debug("environment: '#{environment}' cookbook: '#{dep}'")
67
+ if !cookbooks.member?(dep)
68
+ STDERR.puts "ERROR: Cookbook dependency '#{dep}' from environment '#{environment}' is missing from the list of cookbooks in the manifest."
69
+ exit(-1)
70
+ end
71
+ end
72
+ elsif File.exists?("environments/#{environment}.json")
73
+ #load the json, don't symbolize since we don't need json_class
74
+ f = File.read("environments/#{environment}.json")
75
+ JSON.create_id = nil
76
+ envfile = JSON.parse(f, {:symbolize_names => false})
77
+ Spiceweasel::Log.debug("environment: '#{environment}' file: '#{envfile}'")
78
+ #validate that the name inside the file matches
79
+ Spiceweasel::Log.debug("environment: '#{environment}' name: '#{envfile['name']}'")
80
+ if !environment.eql?(envfile['name'])
81
+ STDERR.puts "ERROR: Environment '#{environment}' listed in the manifest does not match the name '#{envfile['name']}' within the 'environments/#{environment}.json' file."
82
+ exit(-1)
83
+ end
84
+ #validate the cookbooks exist if they're mentioned
85
+ envfile['cookbook_versions'].keys.each do |cb|
86
+ Spiceweasel::Log.debug("environment: '#{environment}' cookbook: '#{cb}'")
87
+ if !cookbooks.member?(cb.to_s)
88
+ STDERR.puts "ERROR: Cookbook dependency '#{cb}' from environment '#{environment}' is missing from the list of cookbooks in the manifest."
89
+ exit(-1)
90
+ end
91
+ end
92
+ else #environment is not here
93
+ STDERR.puts "ERROR: Invalid Environment '#{environment}' listed in the manifest but not found in the environments directory."
94
+ exit(-1)
95
+ end
96
+ end
97
+
98
+ def member?(environment)
99
+ environment_list.include?(environment)
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@opscode.com>)
3
+ #
4
+ # Copyright:: 2012, Opscode, Inc <legal@opscode.com>
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 'mixlib/shellout'
20
+
21
+ module Spiceweasel
22
+ class Execute
23
+
24
+ # run the commands passed in
25
+ def initialize(commands)
26
+ # for now we're shelling out
27
+ commands.each do | cmd |
28
+ knife = Mixlib::ShellOut.new(cmd)
29
+ # check for parallel? and eventually use threads
30
+ knife.run_command
31
+ puts cmd
32
+ puts knife.stdout
33
+ puts knife.stderr
34
+ Spiceweasel::Log.debug(cmd)
35
+ Spiceweasel::Log.debug(knife.stdout)
36
+ Spiceweasel::Log.fatal(knife.stderr) if !knife.stderr.empty?
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+
@@ -0,0 +1,130 @@
1
+ #
2
+ # Author:: Geoff Meakin
3
+ # Author:: Matt Ray (<matt@opscode.com>)
4
+ #
5
+ # Copyright:: 2012, Opscode, Inc <legal@opscode.com>
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'chef/cookbook/metadata'
21
+
22
+ module Spiceweasel
23
+ class ExtractLocal
24
+
25
+ def self.parse_objects
26
+ objects = {'cookbooks' => nil, 'roles' => nil, 'environments' => nil, 'data bags' => nil, 'nodes' => nil}
27
+
28
+ # COOKBOOKS
29
+ cookbooks = Array.new
30
+ Dir.glob('cookbooks/*').each do |cookbook_full_path|
31
+ metadata = Chef::Cookbook::Metadata.new
32
+ metadata.from_file("#{cookbook_full_path}/metadata.rb")
33
+ if metadata.name.empty?
34
+ Spiceweasel::Log.fatal("No cookbook name in the #{cookbook_full_path}/metadata.rb.")
35
+ exit(-1)
36
+ end
37
+ Spiceweasel::Log.debug("dir_ext: #{metadata.name} #{metadata.version}")
38
+ cookbooks.push(metadata)
39
+ end
40
+ cookbooks = self.order_cookbooks_by_dependency(cookbooks)
41
+ objects['cookbooks'] = cookbooks unless cookbooks.empty?
42
+
43
+ # ROLES
44
+ roles = []
45
+ Dir.glob("roles/*.{rb,json}").each do |role_full_path|
46
+ role = self.grab_name_from_path(role_full_path)
47
+ Spiceweasel::Log.debug("dir_ext: role: '#{role}'")
48
+ roles << {role => nil}
49
+ end
50
+ objects['roles'] = roles unless roles.nil?
51
+
52
+ # ENVIRONMENTS
53
+ environments = []
54
+ Dir.glob("environments/*.{rb,json}").each do |environment_full_path|
55
+ environment = self.grab_name_from_path(environment_full_path)
56
+ Spiceweasel::Log.debug("dir_ext: environment: '#{environment}'")
57
+ environments << {environment => nil}
58
+ end
59
+ objects['environments'] = environments unless environments.empty?
60
+
61
+ # DATA BAGS
62
+ data_bags = []
63
+ Dir.glob('data_bags/*').each do |data_bag_full_path|
64
+ data_bag = data_bag_full_path.split('/').last
65
+ Spiceweasel::Log.debug("dir_ext: data_bag: '#{data_bag}'")
66
+ data_bag_items = []
67
+ Dir.glob("#{data_bag_full_path}/*.{rb,json}").each do |data_bag_item_full_path|
68
+ Spiceweasel::Log.debug("dir_ext: data_bag: '#{data_bag}':'#{data_bag_item_full_path}'")
69
+ data_bag_items << self.grab_name_from_path(data_bag_item_full_path)
70
+ end if File.directory?(data_bag_full_path)
71
+ data_bags << {data_bag => {'items' => data_bag_items}}
72
+ end
73
+ objects['data bags'] = data_bags unless data_bags.empty?
74
+
75
+ # NODES
76
+ # TODO: Cant use this yet as node_list.rb doesnt support node from file syntax but expects the node info to be part of the objects passed in
77
+ # nodes = []
78
+ # Dir.glob("nodes/*.{rb,json}").each do |node_full_path|
79
+ # node = self.grab_name_from_path(node_full_path)
80
+ # nodes << {node => nil}
81
+ # end
82
+ # objects['nodes'] = nodes unless nodes.empty?
83
+
84
+ objects
85
+ end
86
+
87
+ def self.grab_name_from_path(path)
88
+ name = path.split('/').last.split('.')
89
+ if name.length > 1
90
+ name.pop
91
+ end
92
+ name.join('.')
93
+ end
94
+
95
+ def self.order_cookbooks_by_dependency(cookbooks)
96
+ # Weak algorithm, not particularly elegant, ignores version info as unlikely to have two versions of a cookbook anyway
97
+ # We're going to find the cookbooks with their dependencies matched and keep going until all we have is unmatched deps
98
+ sorted_cookbooks = Array.new
99
+ unsorted_cookbooks = cookbooks
100
+ scount = 0
101
+ #keep looping until no more cookbooks are left or can't remove remainders
102
+ while unsorted_cookbooks.any? and scount < cookbooks.length
103
+ cookbook = unsorted_cookbooks.shift
104
+ Spiceweasel::Log.debug("dir_ext: cookbook.dependencies: '#{cookbook.name}' #{cookbook.dependencies}")
105
+ #if all the cookbook dependencies are in sorted_cookbooks
106
+ if sorted_cookbooks.eql?(sorted_cookbooks | cookbook.dependencies.collect {|x| x[0]})
107
+ sorted_cookbooks.push(cookbook.name)
108
+ scount = 0
109
+ else #put it back in the list
110
+ unsorted_cookbooks.push(cookbook)
111
+ scount = scount + 1
112
+ end
113
+ Spiceweasel::Log.debug("dir_ext: sorted_cookbooks: '#{sorted_cookbooks}' #{scount}")
114
+ end
115
+ if scount > 0
116
+ remainders = unsorted_cookbooks.collect {|x| x.name}
117
+ Spiceweasel::Log.debug("dir_ext: remainders: '#{remainders}'")
118
+ if Spiceweasel::Config[:novalidation] #stuff is missing, oh well
119
+ sorted_cookbooks.push(remainders).flatten!
120
+ else
121
+ deps = unsorted_cookbooks.collect {|x| x.dependencies.collect {|x| x[0]} - sorted_cookbooks}
122
+ STDERR.puts "ERROR: Dependencies not satisfied or circular dependencies in cookbook(s): #{remainders} depend(s) on #{deps}"
123
+ exit(-1)
124
+ end
125
+ end
126
+ #hack to get the format same as yaml/json parse
127
+ sorted_cookbooks.collect { |x| { x => nil } }
128
+ end
129
+ end
130
+ end