spiceweasel 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +201 -0
- data/README.md +141 -241
- data/bin/spiceweasel +2 -104
- data/lib/spiceweasel.rb +5 -11
- data/lib/spiceweasel/cli.rb +249 -83
- data/lib/spiceweasel/clusters.rb +53 -0
- data/lib/spiceweasel/config.rb +46 -0
- data/lib/spiceweasel/cookbooks.rb +105 -0
- data/lib/spiceweasel/data_bags.rb +89 -0
- data/lib/spiceweasel/environments.rb +103 -0
- data/lib/spiceweasel/execute.rb +42 -0
- data/lib/spiceweasel/extract_local.rb +130 -0
- data/lib/spiceweasel/log.rb +30 -0
- data/lib/spiceweasel/nodes.rb +126 -0
- data/lib/spiceweasel/roles.rb +131 -0
- data/lib/spiceweasel/version.rb +1 -1
- data/spec/bin/spiceweasel_spec.rb +44 -12
- metadata +106 -25
- data/lib/spiceweasel/cookbook_data.rb +0 -66
- data/lib/spiceweasel/cookbook_list.rb +0 -96
- data/lib/spiceweasel/data_bag_list.rb +0 -81
- data/lib/spiceweasel/directory_extractor.rb +0 -121
- data/lib/spiceweasel/environment_list.rb +0 -96
- data/lib/spiceweasel/node_list.rb +0 -98
- data/lib/spiceweasel/role_list.rb +0 -124
@@ -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
|