spiceweasel 0.7.1 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +3 -1
- data/bin/spiceweasel +23 -193
- data/lib/spiceweasel.rb +7 -1
- data/lib/spiceweasel/cli.rb +47 -0
- data/lib/spiceweasel/cookbook_list.rb +39 -0
- data/lib/spiceweasel/data_bag_list.rb +32 -0
- data/lib/spiceweasel/environment_list.rb +18 -0
- data/lib/spiceweasel/node_list.rb +38 -0
- data/lib/spiceweasel/role_list.rb +18 -0
- data/lib/spiceweasel/run_list.rb +36 -0
- data/lib/spiceweasel/version.rb +1 -1
- data/spec/bin/spiceweasel_spec.rb +39 -0
- metadata +60 -32
- data/.gitignore +0 -4
- data/CHANGELOG.md +0 -83
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/example.json +0 -73
- data/example.yml +0 -41
- data/spiceweasel.gemspec +0 -25
- data/test.sh +0 -48
data/README.md
CHANGED
@@ -2,6 +2,8 @@ Description
|
|
2
2
|
===========
|
3
3
|
Spiceweasel is a command-line tool for batch loading Chef infrastructure. It provides a simple syntax in either JSON or YAML for describing and deploying infrastructure in order with the Chef command-line tool `knife`.
|
4
4
|
|
5
|
+
The `examples` directory provides examples based on the Quick Starts provided at [http://help.opscode.com/kb/otherhelp](http://help.opscode.com/kb/otherhelp).
|
6
|
+
|
5
7
|
CHANGELOG.md covers current, previous and future development milestones and contains the features backlog.
|
6
8
|
|
7
9
|
Requirements
|
@@ -12,7 +14,7 @@ Written with Chef 0.9.12 and 0.10.0 and supports cookbooks, environments, roles,
|
|
12
14
|
|
13
15
|
Testing
|
14
16
|
-------
|
15
|
-
Tested with Ubuntu 10.04 and
|
17
|
+
Tested with Ubuntu 10.04 and Chef 0.9.16 and 0.10.2.
|
16
18
|
|
17
19
|
File Syntax
|
18
20
|
===========
|
data/bin/spiceweasel
CHANGED
@@ -1,92 +1,17 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'json'
|
4
|
-
require 'mixlib/cli'
|
5
|
-
require 'spiceweasel/version'
|
6
4
|
require 'yaml'
|
7
5
|
|
8
|
-
|
9
|
-
def validate_run_list(run_list, cookbooks, environments, roles)
|
10
|
-
run_list.each do |item|
|
11
|
-
if item.start_with?("recipe")
|
12
|
-
#recipe[foo] or recipe[foo::bar]
|
13
|
-
recipe = item.slice(7..-2).split("::")[0]
|
14
|
-
unless cookbooks.member?(recipe)
|
15
|
-
raise "'#{item}' is an invalid run_list recipe not managed by spiceweasel"
|
16
|
-
exit(-1)
|
17
|
-
end
|
18
|
-
elsif item.start_with?("environment")
|
19
|
-
#environment[blah]
|
20
|
-
environment = item.slice(12..-2)
|
21
|
-
unless environments.member?(environment)
|
22
|
-
raise "'#{item}' is an invalid run_list environment not managed by spiceweasel"
|
23
|
-
exit(-1)
|
24
|
-
end
|
25
|
-
elsif item.start_with?("role")
|
26
|
-
#role[blah]
|
27
|
-
role = item.slice(5..-2)
|
28
|
-
unless roles.member?(role)
|
29
|
-
raise "'#{item}' is an invalid run_list role not managed by spiceweasel"
|
30
|
-
exit(-1)
|
31
|
-
end
|
32
|
-
else
|
33
|
-
raise "'#{item}' is an invalid run_list component"
|
34
|
-
exit(-1)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class SpiceweaselCLI
|
40
|
-
include Mixlib::CLI
|
41
|
-
|
42
|
-
banner("Usage: spiceweasel [option] file")
|
43
|
-
|
44
|
-
option :debug,
|
45
|
-
:long => "--debug",
|
46
|
-
:description => "Verbose debugging messages.",
|
47
|
-
:boolean => true
|
48
|
-
|
49
|
-
option :delete,
|
50
|
-
:short => "-d",
|
51
|
-
:long => "--delete",
|
52
|
-
:description => "Print the knife commands to be delete the infrastructure",
|
53
|
-
:boolean => true
|
54
|
-
|
55
|
-
option :dryrun,
|
56
|
-
:long => "--dryrun",
|
57
|
-
:description => "Print the knife commands to be executed to STDOUT",
|
58
|
-
:boolean => true
|
59
|
-
|
60
|
-
option :help,
|
61
|
-
:short => "-h",
|
62
|
-
:long => "--help",
|
63
|
-
:description => "Show this message",
|
64
|
-
:on => :tail,
|
65
|
-
:boolean => true,
|
66
|
-
:show_options => true,
|
67
|
-
:exit => 0
|
68
|
-
|
69
|
-
option :rebuild,
|
70
|
-
:short => "-r",
|
71
|
-
:long => "--rebuild",
|
72
|
-
:description => "Print the knife commands to be delete and recreate the infrastructure",
|
73
|
-
:boolean => true
|
74
|
-
|
75
|
-
option :version,
|
76
|
-
:short => "-v",
|
77
|
-
:long => "--version",
|
78
|
-
:description => "Version",
|
79
|
-
:boolean => true,
|
80
|
-
:proc => lambda {|v| puts "Spiceweasel: #{Spiceweasel::VERSION}" },
|
81
|
-
:exit => 0
|
82
|
-
end
|
6
|
+
require 'spiceweasel'
|
83
7
|
|
84
8
|
#process command line options
|
85
9
|
begin
|
86
|
-
ARGV << "-h" if ARGV.empty?
|
87
|
-
cli =
|
10
|
+
ARGV << "-h" if ARGV.empty?
|
11
|
+
cli = Spiceweasel::CLI.new
|
88
12
|
cli.parse_options
|
89
|
-
|
13
|
+
DEBUG = cli.config[:debug]
|
14
|
+
rescue OptionParser::InvalidOption => e
|
90
15
|
STDERR.puts e.message
|
91
16
|
puts cli.opt_parser.to_s
|
92
17
|
exit(-1)
|
@@ -94,7 +19,7 @@ end
|
|
94
19
|
|
95
20
|
begin
|
96
21
|
file = ARGV.last
|
97
|
-
STDOUT.puts "DEBUG: file: #{file}" if cli.config[:debug]
|
22
|
+
STDOUT.puts "DEBUG: file: #{file}" if cli.config[:debug]
|
98
23
|
if (file.end_with?(".yml"))
|
99
24
|
input = YAML.load_file ARGV.last
|
100
25
|
elsif (file.end_with?(".json"))
|
@@ -116,130 +41,35 @@ end
|
|
116
41
|
create = String.new()
|
117
42
|
delete = String.new()
|
118
43
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
cb = cookbook.keys.first
|
124
|
-
if cookbook[cb] and cookbook[cb].length > 0
|
125
|
-
version = cookbook[cb][0].to_s || ""
|
126
|
-
args = cookbook[cb][1] || ""
|
127
|
-
end
|
128
|
-
STDOUT.puts "DEBUG: cookbook: #{cb} #{version}" if cli.config[:debug]
|
129
|
-
delete += "knife cookbook delete #{cb} #{version} -y\n"
|
130
|
-
if File.directory?("cookbooks")
|
131
|
-
if version and File.directory?("cookbooks/#{cb}")
|
132
|
-
#check metadata.rb for requested version
|
133
|
-
metadata = File.open("cookbooks/#{cb}/metadata.rb").grep(/^version/)[0].split()[1].gsub(/"/,'').to_s
|
134
|
-
if (metadata != version)
|
135
|
-
raise "Invalid version #{version} of '#{cb}' requested, #{metadata} is already in the cookbooks directory."
|
136
|
-
exit(-1)
|
137
|
-
end
|
138
|
-
elsif !File.directory?("cookbooks/#{cb}")
|
139
|
-
create += "knife cookbook site download #{cb} #{version} --file cookbooks/#{cb}.tgz #{args}\n"
|
140
|
-
create += "tar -C cookbooks/ -xf cookbooks/#{cb}.tgz\n"
|
141
|
-
end
|
142
|
-
else
|
143
|
-
STDERR.puts "cookbooks directory not found, validation and downloading skipped"
|
144
|
-
end
|
145
|
-
create += "knife cookbook upload #{cb}\n"
|
146
|
-
#flatten list of cookbooks for validation later
|
147
|
-
cookbook_list.push(cb)
|
148
|
-
end
|
44
|
+
cookbook_list = Spiceweasel::CookbookList.new(input['cookbooks'])
|
45
|
+
environment_list = Spiceweasel::EnvironmentList.new(input['environments'])
|
46
|
+
role_list = Spiceweasel::RoleList.new(input['roles'])
|
47
|
+
data_bag_list = Spiceweasel::DataBagList.new(input['data bags'])
|
149
48
|
|
150
|
-
|
151
|
-
environments = input['environments'] || []
|
152
|
-
environment_list = []
|
153
|
-
environments.each do |environment|
|
154
|
-
STDOUT.puts "DEBUG: environment: #{environment.keys[0]}" if cli.config[:debug]
|
155
|
-
delete += "knife environment delete #{environment.keys[0]} -y\n"
|
156
|
-
create += "knife environment from file #{environment.keys[0]}.rb\n"
|
157
|
-
#flatten list of environments for validation later
|
158
|
-
environment_list.push(environment.keys[0])
|
159
|
-
end
|
49
|
+
node_list = Spiceweasel::NodeList.new(input['nodes'], cookbook_list, environment_list, role_list)
|
160
50
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
delete += "knife role delete #{role.keys[0]} -y\n"
|
167
|
-
create += "knife role from file #{role.keys[0]}.rb\n"
|
168
|
-
#flatten list of roles for validation later
|
169
|
-
role_list.push(role.keys[0])
|
170
|
-
end
|
51
|
+
create += cookbook_list.create
|
52
|
+
create += environment_list.create
|
53
|
+
create += role_list.create
|
54
|
+
create += data_bag_list.create
|
55
|
+
create += node_list.create
|
171
56
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
create += "knife data bag create #{bag.keys[0]}\n"
|
178
|
-
items = bag[bag.keys[0]] || []
|
179
|
-
secret = nil
|
180
|
-
while item = items.shift
|
181
|
-
STDOUT.puts "DEBUG: data bag #{bag.keys[0]} item: #{item}" if cli.config[:debug]
|
182
|
-
if item.start_with?("secret")
|
183
|
-
secret = item.split()[1]
|
184
|
-
next
|
185
|
-
end
|
186
|
-
if item =~ /\*/ #wildcard support
|
187
|
-
files = Dir.glob("data_bags/#{bag.keys[0]}/#{item}.json")
|
188
|
-
items += files.collect {|x| x[x.rindex('/')+1..-6]}
|
189
|
-
puts items
|
190
|
-
next
|
191
|
-
end
|
192
|
-
if secret
|
193
|
-
create += "knife data bag from file #{bag.keys[0]} data_bags/#{bag.keys[0]}/#{item}.json --secret-file #{secret}\n"
|
194
|
-
else
|
195
|
-
create += "knife data bag from file #{bag.keys[0]} data_bags/#{bag.keys[0]}/#{item}.json\n"
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
nodes = input['nodes'] || []
|
201
|
-
#currently use bulk_delete for deleting, add provider support real soon
|
202
|
-
delete += "knife node bulk_delete .* -y\n"
|
203
|
-
nodes.each do |node|
|
204
|
-
STDOUT.puts "DEBUG: node: #{node.keys[0]}" if cli.config[:debug]
|
205
|
-
# delete += "knife node delete #{node[node.keys[0]}\n"
|
206
|
-
run_list = node[node.keys[0]][0].gsub(/ /,',').split(',')
|
207
|
-
STDOUT.puts "DEBUG: node run_list: #{run_list}" if cli.config[:debug]
|
208
|
-
validate_run_list(run_list, cookbook_list, environment_list, role_list)
|
209
|
-
#provider support
|
210
|
-
if node.keys[0].start_with?("bluebox","ec2","openstack","rackspace","slicehost","terremark")
|
211
|
-
provider = node.keys[0].split()
|
212
|
-
count = 1
|
213
|
-
if (provider.length == 2)
|
214
|
-
count = provider[1]
|
215
|
-
end
|
216
|
-
#create the instances
|
217
|
-
count.to_i.times do
|
218
|
-
create += "knife #{provider[0]} server create #{node[node.keys[0]][1]}"
|
219
|
-
if run_list.length > 0
|
220
|
-
create += " -r '#{node[node.keys[0]][0].gsub(/ /,',')}'\n"
|
221
|
-
end
|
222
|
-
end
|
223
|
-
else #multinode support
|
224
|
-
node.keys[0].split.each do |server|
|
225
|
-
create += "knife bootstrap #{server} #{node[node.keys[0]][1]}"
|
226
|
-
if run_list.length > 0
|
227
|
-
create += " -r '#{node[node.keys[0]][0].gsub(/ /,',')}'\n"
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
57
|
+
delete += cookbook_list.delete
|
58
|
+
delete += environment_list.delete
|
59
|
+
delete += role_list.delete
|
60
|
+
delete += data_bag_list.delete
|
61
|
+
delete += node_list.delete
|
232
62
|
|
233
63
|
#just print the knife commands, do not execute
|
234
64
|
#if cli.config[:dryrun]
|
235
|
-
if cli.config[:delete]
|
65
|
+
if cli.config[:delete]
|
236
66
|
puts delete unless delete.empty?
|
237
67
|
elsif cli.config[:rebuild]
|
238
68
|
puts delete unless delete.empty?
|
239
69
|
puts create unless create.empty?
|
240
70
|
else
|
241
71
|
puts create unless create.empty?
|
242
|
-
end
|
72
|
+
end
|
243
73
|
#else
|
244
74
|
#eventually we will execute instead of printing knife commands
|
245
75
|
#puts "BAM!"
|
data/lib/spiceweasel.rb
CHANGED
@@ -1,3 +1,9 @@
|
|
1
1
|
module Spiceweasel
|
2
|
-
|
2
|
+
autoload :CLI, 'spiceweasel/cli'
|
3
|
+
autoload :RunList, 'spiceweasel/run_list'
|
4
|
+
autoload :CookbookList, 'spiceweasel/cookbook_list'
|
5
|
+
autoload :EnvironmentList, 'spiceweasel/environment_list'
|
6
|
+
autoload :RoleList, 'spiceweasel/role_list'
|
7
|
+
autoload :DataBagList, 'spiceweasel/data_bag_list'
|
8
|
+
autoload :NodeList, 'spiceweasel/node_list'
|
3
9
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'mixlib/cli'
|
2
|
+
require 'spiceweasel/version'
|
3
|
+
|
4
|
+
class Spiceweasel::CLI
|
5
|
+
include Mixlib::CLI
|
6
|
+
|
7
|
+
banner("Usage: spiceweasel [option] file")
|
8
|
+
|
9
|
+
option :debug,
|
10
|
+
:long => "--debug",
|
11
|
+
:description => "Verbose debugging messages.",
|
12
|
+
:boolean => true
|
13
|
+
|
14
|
+
option :delete,
|
15
|
+
:short => "-d",
|
16
|
+
:long => "--delete",
|
17
|
+
:description => "Print the knife commands to be delete the infrastructure",
|
18
|
+
:boolean => true
|
19
|
+
|
20
|
+
option :dryrun,
|
21
|
+
:long => "--dryrun",
|
22
|
+
:description => "Print the knife commands to be executed to STDOUT",
|
23
|
+
:boolean => true
|
24
|
+
|
25
|
+
option :help,
|
26
|
+
:short => "-h",
|
27
|
+
:long => "--help",
|
28
|
+
:description => "Show this message",
|
29
|
+
:on => :tail,
|
30
|
+
:boolean => true,
|
31
|
+
:show_options => true,
|
32
|
+
:exit => 0
|
33
|
+
|
34
|
+
option :rebuild,
|
35
|
+
:short => "-r",
|
36
|
+
:long => "--rebuild",
|
37
|
+
:description => "Print the knife commands to be delete and recreate the infrastructure",
|
38
|
+
:boolean => true
|
39
|
+
|
40
|
+
option :version,
|
41
|
+
:short => "-v",
|
42
|
+
:long => "--version",
|
43
|
+
:description => "Version",
|
44
|
+
:boolean => true,
|
45
|
+
:proc => lambda {|v| puts "Spiceweasel: #{Spiceweasel::VERSION}" },
|
46
|
+
:exit => 0
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Spiceweasel::CookbookList
|
2
|
+
def initialize(cookbooks = [])
|
3
|
+
@create = @delete = ''
|
4
|
+
@cookbooks = []
|
5
|
+
cookbooks.each do |cookbook|
|
6
|
+
cb = cookbook.keys.first
|
7
|
+
if cookbook[cb] and cookbook[cb].length > 0
|
8
|
+
version = cookbook[cb][0].to_s || ""
|
9
|
+
args = cookbook[cb][1] || ""
|
10
|
+
end
|
11
|
+
STDOUT.puts "DEBUG: cookbook: #{cb} #{version}" if DEBUG
|
12
|
+
@delete += "knife cookbook @delete #{cb} #{version} -y\n"
|
13
|
+
if File.directory?("cookbooks")
|
14
|
+
if version and File.directory?("cookbooks/#{cb}")
|
15
|
+
#check metadata.rb for requested version
|
16
|
+
metadata = File.open("cookbooks/#{cb}/metadata.rb").grep(/^version/)[0].split()[1].gsub(/"/,'').to_s
|
17
|
+
if (metadata != version)
|
18
|
+
raise "Invalid version #{version} of '#{cb}' requested, #{metadata} is already in the cookbooks directory."
|
19
|
+
exit(-1)
|
20
|
+
end
|
21
|
+
elsif !File.directory?("cookbooks/#{cb}")
|
22
|
+
@create += "knife cookbook site download #{cb} #{version} --file cookbooks/#{cb}.tgz #{args}\n"
|
23
|
+
@create += "tar -C cookbooks/ -xf cookbooks/#{cb}.tgz\n"
|
24
|
+
end
|
25
|
+
else
|
26
|
+
STDERR.puts "cookbooks directory not found, validation and downloading skipped"
|
27
|
+
end
|
28
|
+
@create += "knife cookbook upload #{cb}\n"
|
29
|
+
|
30
|
+
@cookbooks << cb
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :cookbooks, :create, :delete
|
35
|
+
|
36
|
+
def member?(cookbook)
|
37
|
+
cookbooks.include?(cookbook)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Spiceweasel::DataBagList
|
2
|
+
def initialize(data_bags = [])
|
3
|
+
@create = @delete = ''
|
4
|
+
data_bags.each do |data_bag|
|
5
|
+
STDOUT.puts "DEBUG: data bag: #{data_bag.keys[0]}" if DEBUG
|
6
|
+
@delete += "knife data bag delete #{data_bag.keys[0]} -y\n"
|
7
|
+
@create += "knife data bag create #{data_bag.keys[0]}\n"
|
8
|
+
items = data_bag[data_bag.keys[0]] || []
|
9
|
+
secret = nil
|
10
|
+
while item = items.shift
|
11
|
+
STDOUT.puts "DEBUG: data bag #{data_bag.keys[0]} item: #{item}" if DEBUG
|
12
|
+
if item.start_with?("secret")
|
13
|
+
secret = item.split()[1]
|
14
|
+
next
|
15
|
+
end
|
16
|
+
if item =~ /\*/ #wildcard support
|
17
|
+
files = Dir.glob("data_bags/#{data_bag.keys[0]}/#{item}.json")
|
18
|
+
items += files.collect {|x| x[x.rindex('/')+1..-6]}
|
19
|
+
puts items
|
20
|
+
next
|
21
|
+
end
|
22
|
+
if secret
|
23
|
+
@create += "knife data bag from file #{data_bag.keys[0]} data_bags/#{data_bag.keys[0]}/#{item}.json --secret-file #{secret}\n"
|
24
|
+
else
|
25
|
+
@create += "knife data bag from file #{data_bag.keys[0]} data_bags/#{data_bag.keys[0]}/#{item}.json\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :create, :delete
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Spiceweasel::EnvironmentList
|
2
|
+
def initialize(environments = [])
|
3
|
+
@create = @delete = ''
|
4
|
+
@environments = []
|
5
|
+
environments.each do |environment|
|
6
|
+
STDOUT.puts "DEBUG: environment: #{environment.keys[0]}" if DEBUG
|
7
|
+
@delete += "knife environment delete #{environment.keys[0]} -y\n"
|
8
|
+
@create += "knife environment from file #{environment.keys[0]}.rb\n"
|
9
|
+
@environments << environment.keys[0]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :environments, :create, :delete
|
14
|
+
|
15
|
+
def member?(environment)
|
16
|
+
environments.include?(environment)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Spiceweasel::NodeList
|
2
|
+
def initialize(nodes, cookbook_list, environment_list, role_list)
|
3
|
+
nodes ||= []
|
4
|
+
@create = @delete = ''
|
5
|
+
|
6
|
+
@delete += "knife node bulk_delete .* -y\n"
|
7
|
+
nodes.each do |node|
|
8
|
+
STDOUT.puts "DEBUG: node: #{node.keys[0]}" if DEBUG
|
9
|
+
run_list = node[node.keys[0]][0].gsub(/ /,',').split(',')
|
10
|
+
STDOUT.puts "DEBUG: node run_list: #{run_list}" if DEBUG
|
11
|
+
Spiceweasel::RunList.new(run_list).validate(cookbook_list, environment_list, role_list)
|
12
|
+
#provider support
|
13
|
+
if node.keys[0].start_with?("bluebox","ec2","openstack","rackspace","slicehost","terremark")
|
14
|
+
provider = node.keys[0].split()
|
15
|
+
count = 1
|
16
|
+
if (provider.length == 2)
|
17
|
+
count = provider[1]
|
18
|
+
end
|
19
|
+
#create the instances
|
20
|
+
count.to_i.times do
|
21
|
+
@create += "knife #{provider[0]} server create #{node[node.keys[0]][1]}"
|
22
|
+
if run_list.length > 0
|
23
|
+
@create += " -r '#{node[node.keys[0]][0].gsub(/ /,',')}'\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
else #multinode support
|
27
|
+
node.keys[0].split.each do |server|
|
28
|
+
@create += "knife bootstrap #{server} #{node[node.keys[0]][1]}"
|
29
|
+
if run_list.length > 0
|
30
|
+
@create += " -r '#{node[node.keys[0]][0].gsub(/ /,',')}'\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :create, :delete
|
38
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Spiceweasel::RoleList
|
2
|
+
def initialize(roles = [])
|
3
|
+
@create = @delete = ''
|
4
|
+
@roles = []
|
5
|
+
roles.each do |role|
|
6
|
+
STDOUT.puts "DEBUG: role: #{role.keys[0]}" if DEBUG
|
7
|
+
@delete += "knife role delete #{role.keys[0]} -y\n"
|
8
|
+
@create += "knife role from file #{role.keys[0]}.rb\n"
|
9
|
+
@roles << role.keys[0]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :roles, :create, :delete
|
14
|
+
|
15
|
+
def member?(role)
|
16
|
+
roles.include?(role)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Spiceweasel::RunList
|
2
|
+
def initialize(run_list)
|
3
|
+
@run_list = run_list
|
4
|
+
end
|
5
|
+
|
6
|
+
def validate(cookbooks, environments, roles)
|
7
|
+
@run_list.each do |item|
|
8
|
+
if item.start_with?("recipe")
|
9
|
+
#recipe[foo] or recipe[foo::bar]
|
10
|
+
recipe = item.slice(7..-2).split("::")[0]
|
11
|
+
unless cookbooks.member?(recipe)
|
12
|
+
raise "'#{item}' is an invalid run_list recipe not managed by spiceweasel"
|
13
|
+
exit(-1)
|
14
|
+
end
|
15
|
+
elsif item.start_with?("environment")
|
16
|
+
#environment[blah]
|
17
|
+
environment = item.slice(12..-2)
|
18
|
+
unless environments.member?(environment)
|
19
|
+
raise "'#{item}' is an invalid run_list environment not managed by spiceweasel"
|
20
|
+
exit(-1)
|
21
|
+
end
|
22
|
+
elsif item.start_with?("role")
|
23
|
+
#role[blah]
|
24
|
+
role = item.slice(5..-2)
|
25
|
+
unless roles.member?(role)
|
26
|
+
raise "'#{item}' is an invalid run_list role not managed by spiceweasel"
|
27
|
+
exit(-1)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
raise "'#{item}' is an invalid run_list component"
|
31
|
+
exit(-1)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/spiceweasel/version.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Cover-all spec to prevent regressions during refactor
|
2
|
+
describe 'The Spiceweasel binary' do
|
3
|
+
before(:each) do
|
4
|
+
@expected_output = <<-OUTPUT
|
5
|
+
knife cookbook upload apache2
|
6
|
+
knife cookbook upload apt
|
7
|
+
knife cookbook upload mysql
|
8
|
+
knife environment from file development.rb
|
9
|
+
knife environment from file qa.rb
|
10
|
+
knife environment from file production.rb
|
11
|
+
knife role from file base.rb
|
12
|
+
knife role from file monitoring.rb
|
13
|
+
knife role from file webserver.rb
|
14
|
+
knife data bag create users
|
15
|
+
knife data bag from file users data_bags/users/alice.json
|
16
|
+
knife data bag from file users data_bags/users/bob.json
|
17
|
+
knife data bag from file users data_bags/users/chuck.json
|
18
|
+
knife data bag create data
|
19
|
+
knife data bag create passwords
|
20
|
+
knife data bag from file passwords data_bags/passwords/mysql.json --secret-file secret_key
|
21
|
+
knife data bag from file passwords data_bags/passwords/rabbitmq.json --secret-file secret_key
|
22
|
+
knife bootstrap serverA -i ~/.ssh/mray.pem -x user --sudo -d ubuntu10.04-gems -r 'role[base]'
|
23
|
+
knife bootstrap serverB -i ~/.ssh/mray.pem -x user --sudo -d ubuntu10.04-gems -r 'role[base]'
|
24
|
+
knife bootstrap serverC -i ~/.ssh/mray.pem -x user --sudo -d ubuntu10.04-gems -r 'role[base]'
|
25
|
+
knife ec2 server create -S mray -i ~/.ssh/mray.pem -x ubuntu -G default -I ami-7000f019 -f m1.small -r 'role[webserver],recipe[mysql::client]'
|
26
|
+
knife ec2 server create -S mray -i ~/.ssh/mray.pem -x ubuntu -G default -I ami-7000f019 -f m1.small -r 'role[webserver],recipe[mysql::client]'
|
27
|
+
knife ec2 server create -S mray -i ~/.ssh/mray.pem -x ubuntu -G default -I ami-7000f019 -f m1.small -r 'role[webserver],recipe[mysql::client]'
|
28
|
+
knife rackspace server create --image 49 --flavor 2 -r 'recipe[mysql],role[monitoring]'
|
29
|
+
knife rackspace server create --image 49 --flavor 2 -r 'recipe[mysql],role[monitoring]'
|
30
|
+
knife rackspace server create --image 49 --flavor 2 -r 'recipe[mysql],role[monitoring]'
|
31
|
+
OUTPUT
|
32
|
+
|
33
|
+
@spiceweasel_binary = File.join(File.dirname(__FILE__), *%w[.. .. bin spiceweasel])
|
34
|
+
end
|
35
|
+
|
36
|
+
it "maintains consistent output from the example config" do
|
37
|
+
`#{@spiceweasel_binary} --dryrun example.yml`.should == @expected_output
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,27 +1,62 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spiceweasel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease: false
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 7
|
9
|
-
- 1
|
10
|
-
version: 0.7.1
|
4
|
+
version: "0.8"
|
11
5
|
platform: ruby
|
12
6
|
authors:
|
13
7
|
- Matt Ray
|
8
|
+
- Elliot Crosby-McCullough
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-
|
13
|
+
date: 2011-07-21 00:00:00 -05:00
|
19
14
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: chef
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: json
|
28
|
+
type: :runtime
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
version:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mixlib-cli
|
38
|
+
type: :runtime
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
type: :development
|
49
|
+
version_requirement:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
22
56
|
description: This provides a CLI for generating knife commands to build Chef-managed infrastructure from a simple YAML file.
|
23
57
|
email:
|
24
58
|
- matt@opscode.com
|
59
|
+
- elliot.cm@gmail.com
|
25
60
|
executables:
|
26
61
|
- spiceweasel
|
27
62
|
extensions: []
|
@@ -29,20 +64,19 @@ extensions: []
|
|
29
64
|
extra_rdoc_files: []
|
30
65
|
|
31
66
|
files:
|
32
|
-
- .gitignore
|
33
|
-
- CHANGELOG.md
|
34
|
-
- Gemfile
|
35
|
-
- README.md
|
36
|
-
- Rakefile
|
37
67
|
- bin/spiceweasel
|
38
|
-
-
|
39
|
-
-
|
40
|
-
- lib/spiceweasel.rb
|
68
|
+
- lib/spiceweasel/cli.rb
|
69
|
+
- lib/spiceweasel/cookbook_list.rb
|
70
|
+
- lib/spiceweasel/data_bag_list.rb
|
71
|
+
- lib/spiceweasel/environment_list.rb
|
72
|
+
- lib/spiceweasel/node_list.rb
|
73
|
+
- lib/spiceweasel/role_list.rb
|
74
|
+
- lib/spiceweasel/run_list.rb
|
41
75
|
- lib/spiceweasel/version.rb
|
42
|
-
- spiceweasel.
|
43
|
-
-
|
76
|
+
- lib/spiceweasel.rb
|
77
|
+
- README.md
|
44
78
|
has_rdoc: true
|
45
|
-
homepage:
|
79
|
+
homepage: http://github.com/mattray/spiceweasel
|
46
80
|
licenses: []
|
47
81
|
|
48
82
|
post_install_message:
|
@@ -51,29 +85,23 @@ rdoc_options: []
|
|
51
85
|
require_paths:
|
52
86
|
- lib
|
53
87
|
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
-
none: false
|
55
88
|
requirements:
|
56
89
|
- - ">="
|
57
90
|
- !ruby/object:Gem::Version
|
58
|
-
hash: 3
|
59
|
-
segments:
|
60
|
-
- 0
|
61
91
|
version: "0"
|
92
|
+
version:
|
62
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
-
none: false
|
64
94
|
requirements:
|
65
95
|
- - ">="
|
66
96
|
- !ruby/object:Gem::Version
|
67
|
-
hash: 3
|
68
|
-
segments:
|
69
|
-
- 0
|
70
97
|
version: "0"
|
98
|
+
version:
|
71
99
|
requirements: []
|
72
100
|
|
73
101
|
rubyforge_project: spiceweasel
|
74
|
-
rubygems_version: 1.3.
|
102
|
+
rubygems_version: 1.3.5
|
75
103
|
signing_key:
|
76
104
|
specification_version: 3
|
77
105
|
summary: CLI for generating Chef knife commands from a simple YAML file.
|
78
|
-
test_files:
|
79
|
-
|
106
|
+
test_files:
|
107
|
+
- spec/bin/spiceweasel_spec.rb
|
data/.gitignore
DELETED
data/CHANGELOG.md
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
This is the current, previous and future development milestones and contains the features backlog.
|
2
|
-
|
3
|
-
0.1
|
4
|
-
===
|
5
|
-
* initial README.md describing goals
|
6
|
-
* command-line tool
|
7
|
-
* basic options all supported
|
8
|
-
* create repo on GitHub
|
9
|
-
* publish as a gem on RubyGems
|
10
|
-
|
11
|
-
0.2
|
12
|
-
===
|
13
|
-
* switch to mixlib-cli
|
14
|
-
* --dryrun The option `--dryrun` will print the commands to run, but not actually execute them (the default behavior)
|
15
|
-
* --delete The option `--delete` will delete each cookbook, role, data bag, environment and node described in the yml file. All nodes from the system are deleted with `knife node bulk_delete`.
|
16
|
-
* --rebuild The option `--rebuild` will remove all currently managed infrastructure for this chef repository and rebuild it from scratch.
|
17
|
-
|
18
|
-
0.3
|
19
|
-
===
|
20
|
-
* renamed MILESTONES.md to CHANGELOG.md
|
21
|
-
* fixed version number
|
22
|
-
* updated YAML schema and examples because Ruby 1.8 does not order hashes.
|
23
|
-
* validate that the recipes and roles listed in the nodes are loaded
|
24
|
-
|
25
|
-
0.4
|
26
|
-
===
|
27
|
-
* support versions for cookbooks
|
28
|
-
* support site vendor for cookbooks
|
29
|
-
|
30
|
-
0.5
|
31
|
-
===
|
32
|
-
* support JSON and YAML
|
33
|
-
|
34
|
-
0.6
|
35
|
-
===
|
36
|
-
* add support for cookbook options
|
37
|
-
|
38
|
-
0.7
|
39
|
-
=============
|
40
|
-
* add support for environments
|
41
|
-
* rescue from parser errors
|
42
|
-
* update cookbook download syntax
|
43
|
-
* multiple nodes with same runlists syntax
|
44
|
-
* add support for encrypted data bags
|
45
|
-
* wildcard support for data bag items
|
46
|
-
|
47
|
-
0.7.1 *CURRENT*
|
48
|
-
===============
|
49
|
-
* fixed run list parsing
|
50
|
-
* updated examples for Chef 0.10
|
51
|
-
* relaxed validation on existing cookbooks
|
52
|
-
|
53
|
-
BACKLOG
|
54
|
-
=======
|
55
|
-
Next
|
56
|
-
----
|
57
|
-
* make spiceweasel a library rather than an executable
|
58
|
-
* switch to using lib/ directory
|
59
|
-
* convert to a knife plugin
|
60
|
-
* knife batch create from file infrastructure.yml
|
61
|
-
* knife batch delete from file infrastructure.json
|
62
|
-
* knife batch rebuild from file infrastructure.yml
|
63
|
-
Future
|
64
|
-
------
|
65
|
-
* --chef-client The option `--chef-client` will make a `knife ssh` call to each box and run `chef-client` on each.
|
66
|
-
* --chef-client validation that nodes are added
|
67
|
-
* -e/--execute execute the commands
|
68
|
-
* catching return codes and retrying (with retry count?)
|
69
|
-
* make the JSON calls directly with Chef APIs
|
70
|
-
* execution-phase validation
|
71
|
-
* check metadata.rb of cookbooks for their dependencies
|
72
|
-
* validate within role files rather than the names of files (assumption that they are the same)
|
73
|
-
* validate cookbooks referenced in roles
|
74
|
-
* validate not trying to use environments with 0.9.x
|
75
|
-
* validate within environment files rather than the names of files (assumption that they are the same)
|
76
|
-
* validate cookbooks referenced in environments
|
77
|
-
* validate recipes from cookbooks in run_lists
|
78
|
-
* wildcards for environments and roles
|
79
|
-
* on provider delete take count of vendor-specific, delete if match (ec2 server delete and node delete)
|
80
|
-
* knife winrm bootstrap FQDN [RUN LIST...] (options)
|
81
|
-
* use GNU parallel with knife?
|
82
|
-
* extract existing infrastructure
|
83
|
-
* knife batch extract
|
data/Gemfile
DELETED
data/Rakefile
DELETED
data/example.json
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"cookbooks":
|
3
|
-
[
|
4
|
-
{"apache2":[]},
|
5
|
-
{"apt":
|
6
|
-
[
|
7
|
-
"1.1.1"
|
8
|
-
]
|
9
|
-
},
|
10
|
-
{"mysql":[]}
|
11
|
-
],
|
12
|
-
"environments":
|
13
|
-
[
|
14
|
-
{"development":[]},
|
15
|
-
{"qa":[]},
|
16
|
-
{"production":[]}
|
17
|
-
],
|
18
|
-
"roles":
|
19
|
-
[
|
20
|
-
{"base":[]},
|
21
|
-
{"monitoring":[]},
|
22
|
-
{"webserver":[]}
|
23
|
-
],
|
24
|
-
"data bags":
|
25
|
-
[
|
26
|
-
{"users":
|
27
|
-
[
|
28
|
-
"alice",
|
29
|
-
"bob",
|
30
|
-
"chuck"
|
31
|
-
]
|
32
|
-
},
|
33
|
-
{"data":
|
34
|
-
[
|
35
|
-
"*"
|
36
|
-
]
|
37
|
-
},
|
38
|
-
{"passwords":
|
39
|
-
[
|
40
|
-
"secret secret_key",
|
41
|
-
"mysql",
|
42
|
-
"rabbitmq"
|
43
|
-
]
|
44
|
-
}
|
45
|
-
],
|
46
|
-
"nodes":
|
47
|
-
[
|
48
|
-
{"serverA":
|
49
|
-
[
|
50
|
-
"role[base]",
|
51
|
-
"-i ~/.ssh/mray.pem -x user --sudo -d ubuntu10.04-gems"
|
52
|
-
]
|
53
|
-
},
|
54
|
-
{"serverB serverC":
|
55
|
-
[
|
56
|
-
"role[base]",
|
57
|
-
"-i ~/.ssh/mray.pem -x user --sudo -d ubuntu10.04-gems"
|
58
|
-
]
|
59
|
-
},
|
60
|
-
{"ec2 3":
|
61
|
-
[
|
62
|
-
"role[webserver] recipe[mysql::client]",
|
63
|
-
"-S mray -i ~/.ssh/mray.pem -x ubuntu -G default -I ami-7000f019 -f m1.small"
|
64
|
-
]
|
65
|
-
},
|
66
|
-
{"rackspace 3":
|
67
|
-
[
|
68
|
-
"recipe[mysql],role[monitoring]",
|
69
|
-
"--image 49 --flavor 2"
|
70
|
-
]
|
71
|
-
}
|
72
|
-
]
|
73
|
-
}
|
data/example.yml
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
cookbooks:
|
2
|
-
- apache2:
|
3
|
-
- apt:
|
4
|
-
- 1.1.1
|
5
|
-
- mysql:
|
6
|
-
|
7
|
-
environments:
|
8
|
-
- development:
|
9
|
-
- qa:
|
10
|
-
- production:
|
11
|
-
|
12
|
-
roles:
|
13
|
-
- base:
|
14
|
-
- monitoring:
|
15
|
-
- webserver:
|
16
|
-
|
17
|
-
data bags:
|
18
|
-
- users:
|
19
|
-
- alice
|
20
|
-
- bob
|
21
|
-
- chuck
|
22
|
-
- data:
|
23
|
-
- *
|
24
|
-
- passwords:
|
25
|
-
- secret secret_key
|
26
|
-
- mysql
|
27
|
-
- rabbitmq
|
28
|
-
|
29
|
-
nodes:
|
30
|
-
- serverA:
|
31
|
-
- role[base]
|
32
|
-
- -i ~/.ssh/mray.pem -x user --sudo -d ubuntu10.04-gems
|
33
|
-
- serverB serverC:
|
34
|
-
- role[base]
|
35
|
-
- -i ~/.ssh/mray.pem -x user --sudo -d ubuntu10.04-gems
|
36
|
-
- ec2 3:
|
37
|
-
- role[webserver] recipe[mysql::client]
|
38
|
-
- -S mray -i ~/.ssh/mray.pem -x ubuntu -G default -I ami-7000f019 -f m1.small
|
39
|
-
- rackspace 3:
|
40
|
-
- recipe[mysql],role[monitoring]
|
41
|
-
- --image 49 --flavor 2
|
data/spiceweasel.gemspec
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "json"
|
4
|
-
require "mixlib/cli"
|
5
|
-
require "spiceweasel/version"
|
6
|
-
require "yaml"
|
7
|
-
|
8
|
-
|
9
|
-
Gem::Specification.new do |s|
|
10
|
-
s.name = "spiceweasel"
|
11
|
-
s.version = Spiceweasel::VERSION
|
12
|
-
s.platform = Gem::Platform::RUBY
|
13
|
-
s.authors = ["Matt Ray"]
|
14
|
-
s.email = ["matt@opscode.com"]
|
15
|
-
s.homepage = ""
|
16
|
-
s.summary = %q{CLI for generating Chef knife commands from a simple YAML file.}
|
17
|
-
s.description = %q{This provides a CLI for generating knife commands to build Chef-managed infrastructure from a simple YAML file.}
|
18
|
-
|
19
|
-
s.rubyforge_project = "spiceweasel"
|
20
|
-
|
21
|
-
s.files = `git ls-files`.split("\n")
|
22
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
-
s.require_paths = ["lib"]
|
25
|
-
end
|
data/test.sh
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
echo "------------>spiceweasel"
|
2
|
-
spiceweasel
|
3
|
-
echo "------------>Return code:$?"
|
4
|
-
echo "------------>spiceweasel -h"
|
5
|
-
spiceweasel -h
|
6
|
-
echo "------------>Return code:$?"
|
7
|
-
echo "------------>spiceweasel -h example.yml"
|
8
|
-
spiceweasel -h example.yml
|
9
|
-
echo "------------>Return code:$?"
|
10
|
-
echo "------------>spiceweasel -v"
|
11
|
-
spiceweasel -v
|
12
|
-
echo "------------>Return code:$?"
|
13
|
-
echo "------------>spiceweasel -v example.yml"
|
14
|
-
spiceweasel -v example.yml
|
15
|
-
echo "------------>Return code:$?"
|
16
|
-
echo "------------>spiceweasel --version"
|
17
|
-
spiceweasel --version
|
18
|
-
echo "------------>Return code:$?"
|
19
|
-
echo "------------>spiceweasel example.yml"
|
20
|
-
spiceweasel example.yml
|
21
|
-
echo "------------>Return code:$?"
|
22
|
-
echo "------------>spiceweasel --dryrun"
|
23
|
-
spiceweasel --dryrun
|
24
|
-
echo "------------>Return code:$?"
|
25
|
-
echo "------------>spiceweasel --dryrun example.yml"
|
26
|
-
spiceweasel --dryrun example.yml
|
27
|
-
echo "------------>Return code:$?"
|
28
|
-
echo "------------>spiceweasel --help"
|
29
|
-
spiceweasel --help
|
30
|
-
echo "------------>Return code:$?"
|
31
|
-
echo "------------>spiceweasel -d"
|
32
|
-
spiceweasel -d
|
33
|
-
echo "------------>Return code:$?"
|
34
|
-
echo "------------>spiceweasel -d example.yml"
|
35
|
-
spiceweasel -d example.yml
|
36
|
-
echo "------------>Return code:$?"
|
37
|
-
echo "------------>spiceweasel --delete example.yml"
|
38
|
-
spiceweasel --delete example.yml
|
39
|
-
echo "------------>Return code:$?"
|
40
|
-
echo "------------>spiceweasel -r"
|
41
|
-
spiceweasel -r
|
42
|
-
echo "------------>Return code:$?"
|
43
|
-
echo "------------>spiceweasel -r example.yml"
|
44
|
-
spiceweasel -r example.yml
|
45
|
-
echo "------------>Return code:$?"
|
46
|
-
echo "------------>spiceweasel --rebuild example.yml"
|
47
|
-
spiceweasel --rebuild example.yml
|
48
|
-
echo "------------>Return code:$?"
|