ridley 0.7.0.rc4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ridley.rb +2 -0
- data/lib/ridley/chef.rb +10 -0
- data/lib/ridley/chef/cookbook.rb +254 -0
- data/lib/ridley/chef/cookbook/metadata.rb +552 -0
- data/lib/ridley/chef/cookbook/syntax_check.rb +162 -0
- data/lib/ridley/chef/digester.rb +55 -0
- data/lib/ridley/errors.rb +2 -0
- data/lib/ridley/mixin.rb +3 -0
- data/lib/ridley/mixin/checksum.rb +14 -0
- data/lib/ridley/mixin/shell_out.rb +23 -0
- data/lib/ridley/resources/cookbook_resource.rb +44 -4
- data/lib/ridley/version.rb +1 -1
- data/ridley.gemspec +3 -1
- data/spec/fixtures/example_cookbook/README.md +11 -0
- data/spec/fixtures/example_cookbook/attributes/default.rb +6 -0
- data/spec/fixtures/example_cookbook/definitions/bad_def.rb +6 -0
- data/spec/fixtures/example_cookbook/files/default/file.h +2 -0
- data/spec/fixtures/example_cookbook/files/ubuntu/file.h +2 -0
- data/spec/fixtures/example_cookbook/libraries/my_lib.rb +6 -0
- data/spec/fixtures/example_cookbook/metadata.rb +7 -0
- data/spec/fixtures/example_cookbook/providers/defprovider.rb +6 -0
- data/spec/fixtures/example_cookbook/recipes/default.rb +6 -0
- data/spec/fixtures/example_cookbook/resources/defresource.rb +6 -0
- data/spec/fixtures/example_cookbook/templates/default/temp.txt.erb +1 -0
- data/spec/support/filepath_matchers.rb +19 -0
- data/spec/unit/ridley/chef/cookbook_spec.rb +412 -0
- data/spec/unit/ridley/resource_spec.rb +1 -1
- data/spec/unit/ridley/resources/cookbook_resource_spec.rb +1 -1
- metadata +76 -7
data/lib/ridley.rb
CHANGED
@@ -28,8 +28,10 @@ module Ridley
|
|
28
28
|
autoload :Client, 'ridley/client'
|
29
29
|
autoload :Connection, 'ridley/connection'
|
30
30
|
autoload :ChainLink, 'ridley/chain_link'
|
31
|
+
autoload :Chef, 'ridley/chef'
|
31
32
|
autoload :DSL, 'ridley/dsl'
|
32
33
|
autoload :Logging, 'ridley/logging'
|
34
|
+
autoload :Mixin, 'ridley/mixin'
|
33
35
|
autoload :Resource, 'ridley/resource'
|
34
36
|
autoload :SandboxUploader, 'ridley/sandbox_uploader'
|
35
37
|
autoload :SSH, 'ridley/ssh'
|
data/lib/ridley/chef.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module Ridley
|
2
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
3
|
+
#
|
4
|
+
# Classes and modules used for integrating with a Chef Server, the Chef community
|
5
|
+
# site, and Chef Cookbooks
|
6
|
+
module Chef
|
7
|
+
autoload :Cookbook, 'ridley/chef/cookbook'
|
8
|
+
autoload :Digester, 'ridley/chef/digester'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
module Ridley::Chef
|
2
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
3
|
+
class Cookbook
|
4
|
+
autoload :Metadata, 'ridley/chef/cookbook/metadata'
|
5
|
+
autoload :SyntaxCheck, 'ridley/chef/cookbook/syntax_check'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# @param [String] filepath
|
9
|
+
# a path on disk to the location of a file to checksum
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
# a checksum that can be used to uniquely identify the file understood
|
13
|
+
# by a Chef Server.
|
14
|
+
def checksum(filepath)
|
15
|
+
Ridley::Chef::Digester.md5_checksum_for_file(filepath)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates a new instance of Ridley::Chef::Cookbook from a path on disk containing
|
19
|
+
# a Cookbook.
|
20
|
+
#
|
21
|
+
# The name of the Cookbook is determined by the value of the name attribute set in
|
22
|
+
# the cookbooks' metadata. If the name attribute is not present the name of the loaded
|
23
|
+
# cookbook is determined by directory containing the cookbook.
|
24
|
+
#
|
25
|
+
# @param [#to_s] path
|
26
|
+
# a path on disk to the location of a Cookbook
|
27
|
+
#
|
28
|
+
# @option options [String] :name
|
29
|
+
# explicitly supply the name of the cookbook we are loading. This is useful if
|
30
|
+
# you are dealing with a cookbook that does not have well-formed metadata
|
31
|
+
#
|
32
|
+
# @raise [IOError] if the path does not contain a metadata.rb file
|
33
|
+
#
|
34
|
+
# @return [Ridley::Chef::Cookbook]
|
35
|
+
def from_path(path, options = {})
|
36
|
+
path = Pathname.new(path)
|
37
|
+
metadata = Cookbook::Metadata.from_file(path.join('metadata.rb'))
|
38
|
+
|
39
|
+
name = if options[:name].present?
|
40
|
+
options[:name]
|
41
|
+
else
|
42
|
+
metadata.name.empty? ? File.basename(path) : metadata.name
|
43
|
+
end
|
44
|
+
|
45
|
+
new(name, path, metadata)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
CHEF_TYPE = "cookbook_version".freeze
|
50
|
+
CHEF_JSON_CLASS = "Chef::CookbookVersion".freeze
|
51
|
+
|
52
|
+
extend Forwardable
|
53
|
+
|
54
|
+
attr_reader :cookbook_name
|
55
|
+
attr_reader :path
|
56
|
+
attr_reader :metadata
|
57
|
+
# @return [Hashie::Mash]
|
58
|
+
# a Hashie::Mash containing Cookbook file category names as keys and an Array of Hashes
|
59
|
+
# containing metadata about the files belonging to that category. This is used
|
60
|
+
# to communicate what a Cookbook looks like when uploading to a Chef Server.
|
61
|
+
#
|
62
|
+
# example:
|
63
|
+
# {
|
64
|
+
# :recipes => [
|
65
|
+
# {
|
66
|
+
# name: "default.rb",
|
67
|
+
# path: "recipes/default.rb",
|
68
|
+
# checksum: "fb1f925dcd5fc4ebf682c4442a21c619",
|
69
|
+
# specificity: "default"
|
70
|
+
# }
|
71
|
+
# ]
|
72
|
+
# ...
|
73
|
+
# ...
|
74
|
+
# }
|
75
|
+
attr_reader :manifest
|
76
|
+
|
77
|
+
def_delegator :@metadata, :version
|
78
|
+
|
79
|
+
def initialize(name, path, metadata)
|
80
|
+
@cookbook_name = name
|
81
|
+
@path = Pathname.new(path)
|
82
|
+
@metadata = metadata
|
83
|
+
@files = Array.new
|
84
|
+
@manifest = Hashie::Mash.new(
|
85
|
+
recipes: Array.new,
|
86
|
+
definitions: Array.new,
|
87
|
+
libraries: Array.new,
|
88
|
+
attributes: Array.new,
|
89
|
+
files: Array.new,
|
90
|
+
templates: Array.new,
|
91
|
+
resources: Array.new,
|
92
|
+
providers: Array.new,
|
93
|
+
root_files: Array.new
|
94
|
+
)
|
95
|
+
|
96
|
+
load_files
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [Hash]
|
100
|
+
# an hash containing the checksums and expanded file paths of all of the
|
101
|
+
# files found in the instance of CachedCookbook
|
102
|
+
#
|
103
|
+
# example:
|
104
|
+
# {
|
105
|
+
# "da97c94bb6acb2b7900cbf951654fea3" => "/Users/reset/.ridley/nginx-0.101.2/README.md"
|
106
|
+
# }
|
107
|
+
def checksums
|
108
|
+
{}.tap do |checksums|
|
109
|
+
files.each do |file|
|
110
|
+
checksums[self.class.checksum(file)] = file
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param [Symbol] category
|
116
|
+
# the category of file to generate metadata about
|
117
|
+
# @param [String] target
|
118
|
+
# the filepath to the file to get metadata information about
|
119
|
+
#
|
120
|
+
# @return [Hash]
|
121
|
+
# a Hash containing a name, path, checksum, and specificity key representing the
|
122
|
+
# metadata about a file contained in a Cookbook. This metadata is used when
|
123
|
+
# uploading a Cookbook's files to a Chef Server.
|
124
|
+
#
|
125
|
+
# @example
|
126
|
+
# file_metadata(:root_files, "somefile.h") => {
|
127
|
+
# name: "default.rb",
|
128
|
+
# path: "recipes/default.rb",
|
129
|
+
# checksum: "fb1f925dcd5fc4ebf682c4442a21c619",
|
130
|
+
# specificity: "default"
|
131
|
+
# }
|
132
|
+
def file_metadata(category, target)
|
133
|
+
target = Pathname.new(target)
|
134
|
+
|
135
|
+
{
|
136
|
+
name: target.basename.to_s,
|
137
|
+
path: target.relative_path_from(path).to_s,
|
138
|
+
checksum: self.class.checksum(target),
|
139
|
+
specificity: file_specificity(category, target)
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param [Symbol] category
|
144
|
+
# @param [Pathname] target
|
145
|
+
#
|
146
|
+
# @return [String]
|
147
|
+
def file_specificity(category, target)
|
148
|
+
case category
|
149
|
+
when :files, :templates
|
150
|
+
relpath = target.relative_path_from(path).to_s
|
151
|
+
relpath.slice(/(.+)\/(.+)\/.+/, 2)
|
152
|
+
else
|
153
|
+
'default'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# @return [String]
|
158
|
+
# the name of the cookbook and the version number separated by a dash (-).
|
159
|
+
#
|
160
|
+
# example:
|
161
|
+
# "nginx-0.101.2"
|
162
|
+
def name
|
163
|
+
"#{cookbook_name}-#{version}"
|
164
|
+
end
|
165
|
+
|
166
|
+
def validate
|
167
|
+
raise IOError, "No Cookbook found at: #{path}" unless path.exist?
|
168
|
+
|
169
|
+
unless quietly { syntax_checker.validate_ruby_files }
|
170
|
+
raise Ridley::Errors::CookbookSyntaxError, "Invalid ruby files in cookbook: #{name} (#{version})."
|
171
|
+
end
|
172
|
+
unless quietly { syntax_checker.validate_templates }
|
173
|
+
raise Ridley::Errors::CookbookSyntaxError, "Invalid template files in cookbook: #{name} (#{version})."
|
174
|
+
end
|
175
|
+
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
def to_hash
|
180
|
+
result = manifest.dup
|
181
|
+
result[:chef_type] = CHEF_TYPE
|
182
|
+
result[:name] = name
|
183
|
+
result[:cookbook_name] = cookbook_name
|
184
|
+
result[:version] = version
|
185
|
+
result[:metadata] = metadata
|
186
|
+
result.to_hash
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_json(*args)
|
190
|
+
result = self.to_hash
|
191
|
+
result['json_class'] = CHEF_JSON_CLASS
|
192
|
+
result['frozen?'] = false
|
193
|
+
result.to_json(*args)
|
194
|
+
end
|
195
|
+
|
196
|
+
def to_s
|
197
|
+
"#{cookbook_name} (#{version}) '#{path}'"
|
198
|
+
end
|
199
|
+
|
200
|
+
def <=>(other)
|
201
|
+
[self.cookbook_name, self.version] <=> [other.cookbook_name, other.version]
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
attr_reader :files
|
207
|
+
|
208
|
+
def load_files
|
209
|
+
load_shallow(:recipes, 'recipes', '*.rb')
|
210
|
+
load_shallow(:definitions, 'definitions', '*.rb')
|
211
|
+
load_shallow(:libraries, 'libraries', '*.rb')
|
212
|
+
load_shallow(:attributes, 'attributes', '*.rb')
|
213
|
+
load_recursively(:files, "files", "*")
|
214
|
+
load_recursively(:templates, "templates", "*")
|
215
|
+
load_recursively(:resources, "resources", "*.rb")
|
216
|
+
load_recursively(:providers, "providers", "*.rb")
|
217
|
+
load_root
|
218
|
+
end
|
219
|
+
|
220
|
+
def load_root
|
221
|
+
[].tap do |files|
|
222
|
+
Dir.glob(path.join('*'), File::FNM_DOTMATCH).each do |file|
|
223
|
+
next if File.directory?(file)
|
224
|
+
@files << file
|
225
|
+
@manifest[:root_files] << file_metadata(:root_files, file)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def load_recursively(category, category_dir, glob)
|
231
|
+
[].tap do |files|
|
232
|
+
file_spec = path.join(category_dir, '**', glob)
|
233
|
+
Dir.glob(file_spec, File::FNM_DOTMATCH).each do |file|
|
234
|
+
next if File.directory?(file)
|
235
|
+
@files << file
|
236
|
+
@manifest[category] << file_metadata(category, file)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def load_shallow(category, *path_glob)
|
242
|
+
[].tap do |files|
|
243
|
+
Dir[path.join(*path_glob)].each do |file|
|
244
|
+
@files << file
|
245
|
+
@manifest[category] << file_metadata(category, file)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def syntax_checker
|
251
|
+
@syntax_checker ||= Cookbook::SyntaxCheck.new(path.to_s)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,552 @@
|
|
1
|
+
module Ridley::Chef
|
2
|
+
class Cookbook
|
3
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
4
|
+
#
|
5
|
+
# Borrowed and modified from: {https://raw.github.com/opscode/chef/11.4.0/lib/chef/cookbook/metadata.rb}
|
6
|
+
#
|
7
|
+
# Copyright:: Copyright 2008-2010 Opscode, Inc.
|
8
|
+
#
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
10
|
+
# you may not use this file except in compliance with the License.
|
11
|
+
# You may obtain a copy of the License at
|
12
|
+
#
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
14
|
+
#
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
18
|
+
# See the License for the specific language governing permissions and
|
19
|
+
# limitations under the License.
|
20
|
+
#
|
21
|
+
# == Chef::Cookbook::Metadata
|
22
|
+
# Chef::Cookbook::Metadata provides a convenient DSL for declaring metadata
|
23
|
+
# about Chef Cookbooks.
|
24
|
+
class Metadata
|
25
|
+
class << self
|
26
|
+
def from_hash(hash)
|
27
|
+
new.from_hash(hash)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
NAME = 'name'.freeze
|
32
|
+
DESCRIPTION = 'description'.freeze
|
33
|
+
LONG_DESCRIPTION = 'long_description'.freeze
|
34
|
+
MAINTAINER = 'maintainer'.freeze
|
35
|
+
MAINTAINER_EMAIL = 'maintainer_email'.freeze
|
36
|
+
LICENSE = 'license'.freeze
|
37
|
+
PLATFORMS = 'platforms'.freeze
|
38
|
+
DEPENDENCIES = 'dependencies'.freeze
|
39
|
+
RECOMMENDATIONS = 'recommendations'.freeze
|
40
|
+
SUGGESTIONS = 'suggestions'.freeze
|
41
|
+
CONFLICTING = 'conflicting'.freeze
|
42
|
+
PROVIDING = 'providing'.freeze
|
43
|
+
REPLACING = 'replacing'.freeze
|
44
|
+
ATTRIBUTES = 'attributes'.freeze
|
45
|
+
GROUPINGS = 'groupings'.freeze
|
46
|
+
RECIPES = 'recipes'.freeze
|
47
|
+
VERSION = 'version'.freeze
|
48
|
+
|
49
|
+
COMPARISON_FIELDS = [
|
50
|
+
:name, :description, :long_description, :maintainer,
|
51
|
+
:maintainer_email, :license, :platforms, :dependencies,
|
52
|
+
:recommendations, :suggestions, :conflicting, :providing,
|
53
|
+
:replacing, :attributes, :groupings, :recipes, :version
|
54
|
+
]
|
55
|
+
|
56
|
+
include Chozo::Mixin::ParamsValidate
|
57
|
+
include Chozo::Mixin::FromFile
|
58
|
+
|
59
|
+
attr_reader :cookbook
|
60
|
+
attr_reader :platforms
|
61
|
+
attr_reader :dependencies
|
62
|
+
attr_reader :recommendations
|
63
|
+
attr_reader :suggestions
|
64
|
+
attr_reader :conflicting
|
65
|
+
attr_reader :providing
|
66
|
+
attr_reader :replacing
|
67
|
+
attr_reader :attributes
|
68
|
+
attr_reader :groupings
|
69
|
+
attr_reader :recipes
|
70
|
+
attr_reader :version
|
71
|
+
|
72
|
+
# Builds a new Chef::Cookbook::Metadata object.
|
73
|
+
#
|
74
|
+
# === Parameters
|
75
|
+
# cookbook<String>:: An optional cookbook object
|
76
|
+
# maintainer<String>:: An optional maintainer
|
77
|
+
# maintainer_email<String>:: An optional maintainer email
|
78
|
+
# license<String>::An optional license. Default is Apache v2.0
|
79
|
+
#
|
80
|
+
# === Returns
|
81
|
+
# metadata<Chef::Cookbook::Metadata>
|
82
|
+
def initialize(cookbook = nil, maintainer = 'YOUR_COMPANY_NAME', maintainer_email = 'YOUR_EMAIL', license = 'none')
|
83
|
+
@cookbook = cookbook
|
84
|
+
@name = cookbook ? cookbook.name : ""
|
85
|
+
@long_description = ""
|
86
|
+
self.maintainer(maintainer)
|
87
|
+
self.maintainer_email(maintainer_email)
|
88
|
+
self.license(license)
|
89
|
+
self.description('A fabulous new cookbook')
|
90
|
+
@platforms = Hashie::Mash.new
|
91
|
+
@dependencies = Hashie::Mash.new
|
92
|
+
@recommendations = Hashie::Mash.new
|
93
|
+
@suggestions = Hashie::Mash.new
|
94
|
+
@conflicting = Hashie::Mash.new
|
95
|
+
@providing = Hashie::Mash.new
|
96
|
+
@replacing = Hashie::Mash.new
|
97
|
+
@attributes = Hashie::Mash.new
|
98
|
+
@groupings = Hashie::Mash.new
|
99
|
+
@recipes = Hashie::Mash.new
|
100
|
+
@version = Solve::Version.new("0.0.0")
|
101
|
+
if cookbook
|
102
|
+
@recipes = cookbook.fully_qualified_recipe_names.inject({}) do |r, e|
|
103
|
+
e = self.name if e =~ /::default$/
|
104
|
+
r[e] = ""
|
105
|
+
self.provides e
|
106
|
+
r
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def ==(other)
|
112
|
+
COMPARISON_FIELDS.inject(true) do |equal_so_far, field|
|
113
|
+
equal_so_far && other.respond_to?(field) && (other.send(field) == send(field))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sets the cookbooks maintainer, or returns it.
|
118
|
+
#
|
119
|
+
# === Parameters
|
120
|
+
# maintainer<String>:: The maintainers name
|
121
|
+
#
|
122
|
+
# === Returns
|
123
|
+
# maintainer<String>:: Returns the current maintainer.
|
124
|
+
def maintainer(arg = nil)
|
125
|
+
set_or_return(
|
126
|
+
:maintainer,
|
127
|
+
arg,
|
128
|
+
:kind_of => [ String ]
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Sets the maintainers email address, or returns it.
|
133
|
+
#
|
134
|
+
# === Parameters
|
135
|
+
# maintainer_email<String>:: The maintainers email address
|
136
|
+
#
|
137
|
+
# === Returns
|
138
|
+
# maintainer_email<String>:: Returns the current maintainer email.
|
139
|
+
def maintainer_email(arg = nil)
|
140
|
+
set_or_return(
|
141
|
+
:maintainer_email,
|
142
|
+
arg,
|
143
|
+
:kind_of => [ String ]
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Sets the current license, or returns it.
|
148
|
+
#
|
149
|
+
# === Parameters
|
150
|
+
# license<String>:: The current license.
|
151
|
+
#
|
152
|
+
# === Returns
|
153
|
+
# license<String>:: Returns the current license
|
154
|
+
def license(arg = nil)
|
155
|
+
set_or_return(
|
156
|
+
:license,
|
157
|
+
arg,
|
158
|
+
:kind_of => [ String ]
|
159
|
+
)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets the current description, or returns it. Should be short - one line only!
|
163
|
+
#
|
164
|
+
# === Parameters
|
165
|
+
# description<String>:: The new description
|
166
|
+
#
|
167
|
+
# === Returns
|
168
|
+
# description<String>:: Returns the description
|
169
|
+
def description(arg = nil)
|
170
|
+
set_or_return(
|
171
|
+
:description,
|
172
|
+
arg,
|
173
|
+
:kind_of => [ String ]
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Sets the current long description, or returns it. Might come from a README, say.
|
178
|
+
#
|
179
|
+
# === Parameters
|
180
|
+
# long_description<String>:: The new long description
|
181
|
+
#
|
182
|
+
# === Returns
|
183
|
+
# long_description<String>:: Returns the long description
|
184
|
+
def long_description(arg = nil)
|
185
|
+
set_or_return(
|
186
|
+
:long_description,
|
187
|
+
arg,
|
188
|
+
:kind_of => [ String ]
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Sets the current cookbook version, or returns it. Can be two or three digits, seperated
|
193
|
+
# by dots. ie: '2.1', '1.5.4' or '0.9'.
|
194
|
+
#
|
195
|
+
# === Parameters
|
196
|
+
# version<String>:: The curent version, as a string
|
197
|
+
#
|
198
|
+
# === Returns
|
199
|
+
# version<String>:: Returns the current version
|
200
|
+
def version(arg = nil)
|
201
|
+
if arg
|
202
|
+
@version = Solve::Version.new(arg)
|
203
|
+
end
|
204
|
+
|
205
|
+
@version.to_s
|
206
|
+
end
|
207
|
+
|
208
|
+
# Sets the name of the cookbook, or returns it.
|
209
|
+
#
|
210
|
+
# === Parameters
|
211
|
+
# name<String>:: The curent cookbook name.
|
212
|
+
#
|
213
|
+
# === Returns
|
214
|
+
# name<String>:: Returns the current cookbook name.
|
215
|
+
def name(arg = nil)
|
216
|
+
set_or_return(
|
217
|
+
:name,
|
218
|
+
arg,
|
219
|
+
:kind_of => [ String ]
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Adds a supported platform, with version checking strings.
|
224
|
+
#
|
225
|
+
# === Parameters
|
226
|
+
# platform<String>,<Symbol>:: The platform (like :ubuntu or :mac_os_x)
|
227
|
+
# version<String>:: A version constraint of the form "OP VERSION",
|
228
|
+
# where OP is one of < <= = > >= ~> and VERSION has
|
229
|
+
# the form x.y.z or x.y.
|
230
|
+
#
|
231
|
+
# === Returns
|
232
|
+
# versions<Array>:: Returns the list of versions for the platform
|
233
|
+
def supports(platform, *version_args)
|
234
|
+
version = version_args.first
|
235
|
+
@platforms[platform] = Solve::Constraint.new(version).to_s
|
236
|
+
@platforms[platform]
|
237
|
+
rescue Solve::Errors::InvalidConstraintFormat => ex
|
238
|
+
raise InvalidVersionConstraint, ex.to_s
|
239
|
+
end
|
240
|
+
|
241
|
+
# Adds a dependency on another cookbook, with version checking strings.
|
242
|
+
#
|
243
|
+
# === Parameters
|
244
|
+
# cookbook<String>:: The cookbook
|
245
|
+
# version<String>:: A version constraint of the form "OP VERSION",
|
246
|
+
# where OP is one of < <= = > >= ~> and VERSION has
|
247
|
+
# the form x.y.z or x.y.
|
248
|
+
#
|
249
|
+
# === Returns
|
250
|
+
# versions<Array>:: Returns the list of versions for the platform
|
251
|
+
def depends(cookbook, *version_args)
|
252
|
+
version = version_args.first
|
253
|
+
@dependencies[cookbook] = Solve::Constraint.new(version).to_s
|
254
|
+
@dependencies[cookbook]
|
255
|
+
rescue Solve::Errors::InvalidConstraintFormat => ex
|
256
|
+
raise InvalidVersionConstraint, ex.to_s
|
257
|
+
end
|
258
|
+
|
259
|
+
# Adds a recommendation for another cookbook, with version checking strings.
|
260
|
+
#
|
261
|
+
# === Parameters
|
262
|
+
# cookbook<String>:: The cookbook
|
263
|
+
# version<String>:: A version constraint of the form "OP VERSION",
|
264
|
+
# where OP is one of < <= = > >= ~> and VERSION has
|
265
|
+
# the form x.y.z or x.y.
|
266
|
+
#
|
267
|
+
# === Returns
|
268
|
+
# versions<Array>:: Returns the list of versions for the platform
|
269
|
+
def recommends(cookbook, *version_args)
|
270
|
+
version = version_args.first
|
271
|
+
@recommendations[cookbook] = Solve::Constraint.new(version).to_s
|
272
|
+
@recommendations[cookbook]
|
273
|
+
rescue Solve::Errors::InvalidConstraintFormat => ex
|
274
|
+
raise InvalidVersionConstraint, ex.to_s
|
275
|
+
end
|
276
|
+
|
277
|
+
# Adds a suggestion for another cookbook, with version checking strings.
|
278
|
+
#
|
279
|
+
# === Parameters
|
280
|
+
# cookbook<String>:: The cookbook
|
281
|
+
# version<String>:: A version constraint of the form "OP VERSION",
|
282
|
+
# where OP is one of < <= = > >= ~> and VERSION has the
|
283
|
+
# formx.y.z or x.y.
|
284
|
+
#
|
285
|
+
# === Returns
|
286
|
+
# versions<Array>:: Returns the list of versions for the platform
|
287
|
+
def suggests(cookbook, *version_args)
|
288
|
+
version = version_args.first
|
289
|
+
@suggestions[cookbook] = Solve::Constraint.new(version).to_s
|
290
|
+
@suggestions[cookbook]
|
291
|
+
rescue Solve::Errors::InvalidConstraintFormat => ex
|
292
|
+
raise InvalidVersionConstraint, ex.to_s
|
293
|
+
end
|
294
|
+
|
295
|
+
# Adds a conflict for another cookbook, with version checking strings.
|
296
|
+
#
|
297
|
+
# === Parameters
|
298
|
+
# cookbook<String>:: The cookbook
|
299
|
+
# version<String>:: A version constraint of the form "OP VERSION",
|
300
|
+
# where OP is one of < <= = > >= ~> and VERSION has
|
301
|
+
# the form x.y.z or x.y.
|
302
|
+
#
|
303
|
+
# === Returns
|
304
|
+
# versions<Array>:: Returns the list of versions for the platform
|
305
|
+
def conflicts(cookbook, *version_args)
|
306
|
+
version = version_args.first
|
307
|
+
@conflicting[cookbook] = Solve::Constraint.new(version).to_s
|
308
|
+
@conflicting[cookbook]
|
309
|
+
rescue Solve::Errors::InvalidConstraintFormat => ex
|
310
|
+
raise InvalidVersionConstraint, ex.to_s
|
311
|
+
end
|
312
|
+
|
313
|
+
# Adds a recipe, definition, or resource provided by this cookbook.
|
314
|
+
#
|
315
|
+
# Recipes are specified as normal
|
316
|
+
# Definitions are followed by (), and can include :params for prototyping
|
317
|
+
# Resources are the stringified version (service[apache2])
|
318
|
+
#
|
319
|
+
# === Parameters
|
320
|
+
# recipe, definition, resource<String>:: The thing we provide
|
321
|
+
# version<String>:: A version constraint of the form "OP VERSION",
|
322
|
+
# where OP is one of < <= = > >= ~> and VERSION has
|
323
|
+
# the form x.y.z or x.y.
|
324
|
+
#
|
325
|
+
# === Returns
|
326
|
+
# versions<Array>:: Returns the list of versions for the platform
|
327
|
+
def provides(cookbook, *version_args)
|
328
|
+
version = version_args.first
|
329
|
+
@providing[cookbook] = Solve::Constraint.new(version).to_s
|
330
|
+
@providing[cookbook]
|
331
|
+
rescue Solve::Errors::InvalidConstraintFormat => ex
|
332
|
+
raise InvalidVersionConstraint, ex.to_s
|
333
|
+
end
|
334
|
+
|
335
|
+
# Adds a cookbook that is replaced by this one, with version checking strings.
|
336
|
+
#
|
337
|
+
# === Parameters
|
338
|
+
# cookbook<String>:: The cookbook we replace
|
339
|
+
# version<String>:: A version constraint of the form "OP VERSION",
|
340
|
+
# where OP is one of < <= = > >= ~> and VERSION has the form x.y.z or x.y.
|
341
|
+
#
|
342
|
+
# === Returns
|
343
|
+
# versions<Array>:: Returns the list of versions for the platform
|
344
|
+
def replaces(cookbook, *version_args)
|
345
|
+
version = version_args.first
|
346
|
+
@replacing[cookbook] = Solve::Constraint.new(version).to_s
|
347
|
+
@replacing[cookbook]
|
348
|
+
rescue Solve::Errors::InvalidConstraintFormat => ex
|
349
|
+
raise InvalidVersionConstraint, ex.to_s
|
350
|
+
end
|
351
|
+
|
352
|
+
# Adds a description for a recipe.
|
353
|
+
#
|
354
|
+
# === Parameters
|
355
|
+
# recipe<String>:: The recipe
|
356
|
+
# description<String>:: The description of the recipe
|
357
|
+
#
|
358
|
+
# === Returns
|
359
|
+
# description<String>:: Returns the current description
|
360
|
+
def recipe(name, description)
|
361
|
+
@recipes[name] = description
|
362
|
+
end
|
363
|
+
|
364
|
+
# Adds an attribute )hat a user needs to configure for this cookbook. Takes
|
365
|
+
# a name (with the / notation for a nested attribute), followed by any of
|
366
|
+
# these options
|
367
|
+
#
|
368
|
+
# display_name<String>:: What a UI should show for this attribute
|
369
|
+
# description<String>:: A hint as to what this attr is for
|
370
|
+
# choice<Array>:: An array of choices to present to the user.
|
371
|
+
# calculated<Boolean>:: If true, the default value is calculated by the recipe and cannot be displayed.
|
372
|
+
# type<String>:: "string" or "array" - default is "string" ("hash" is supported for backwards compatibility)
|
373
|
+
# required<String>:: Whether this attr is 'required', 'recommended' or 'optional' - default 'optional' (true/false values also supported for backwards compatibility)
|
374
|
+
# recipes<Array>:: An array of recipes which need this attr set.
|
375
|
+
# default<String>,<Array>,<Hash>:: The default value
|
376
|
+
#
|
377
|
+
# === Parameters
|
378
|
+
# name<String>:: The name of the attribute ('foo', or 'apache2/log_dir')
|
379
|
+
# options<Hash>:: The description of the options
|
380
|
+
#
|
381
|
+
# === Returns
|
382
|
+
# options<Hash>:: Returns the current options hash
|
383
|
+
def attribute(name, options)
|
384
|
+
validate(
|
385
|
+
options,
|
386
|
+
{
|
387
|
+
:display_name => { :kind_of => String },
|
388
|
+
:description => { :kind_of => String },
|
389
|
+
:choice => { :kind_of => [ Array ], :default => [] },
|
390
|
+
:calculated => { :equal_to => [ true, false ], :default => false },
|
391
|
+
:type => { :equal_to => [ "string", "array", "hash", "symbol" ], :default => "string" },
|
392
|
+
:required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" },
|
393
|
+
:recipes => { :kind_of => [ Array ], :default => [] },
|
394
|
+
:default => { :kind_of => [ String, Array, Hash ] }
|
395
|
+
}
|
396
|
+
)
|
397
|
+
options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
|
398
|
+
validate_string_array(options[:choice])
|
399
|
+
validate_calculated_default_rule(options)
|
400
|
+
validate_choice_default_rule(options)
|
401
|
+
|
402
|
+
@attributes[name] = options
|
403
|
+
@attributes[name]
|
404
|
+
end
|
405
|
+
|
406
|
+
def grouping(name, options)
|
407
|
+
validate(
|
408
|
+
options,
|
409
|
+
{
|
410
|
+
:title => { :kind_of => String },
|
411
|
+
:description => { :kind_of => String }
|
412
|
+
}
|
413
|
+
)
|
414
|
+
@groupings[name] = options
|
415
|
+
@groupings[name]
|
416
|
+
end
|
417
|
+
|
418
|
+
def to_hash
|
419
|
+
{
|
420
|
+
NAME => self.name,
|
421
|
+
DESCRIPTION => self.description,
|
422
|
+
LONG_DESCRIPTION => self.long_description,
|
423
|
+
MAINTAINER => self.maintainer,
|
424
|
+
MAINTAINER_EMAIL => self.maintainer_email,
|
425
|
+
LICENSE => self.license,
|
426
|
+
PLATFORMS => self.platforms,
|
427
|
+
DEPENDENCIES => self.dependencies,
|
428
|
+
RECOMMENDATIONS => self.recommendations,
|
429
|
+
SUGGESTIONS => self.suggestions,
|
430
|
+
CONFLICTING => self.conflicting,
|
431
|
+
PROVIDING => self.providing,
|
432
|
+
REPLACING => self.replacing,
|
433
|
+
ATTRIBUTES => self.attributes,
|
434
|
+
GROUPINGS => self.groupings,
|
435
|
+
RECIPES => self.recipes,
|
436
|
+
VERSION => self.version
|
437
|
+
}
|
438
|
+
end
|
439
|
+
|
440
|
+
def from_hash(o)
|
441
|
+
@name = o[NAME] if o.has_key?(NAME)
|
442
|
+
@description = o[DESCRIPTION] if o.has_key?(DESCRIPTION)
|
443
|
+
@long_description = o[LONG_DESCRIPTION] if o.has_key?(LONG_DESCRIPTION)
|
444
|
+
@maintainer = o[MAINTAINER] if o.has_key?(MAINTAINER)
|
445
|
+
@maintainer_email = o[MAINTAINER_EMAIL] if o.has_key?(MAINTAINER_EMAIL)
|
446
|
+
@license = o[LICENSE] if o.has_key?(LICENSE)
|
447
|
+
@platforms = o[PLATFORMS] if o.has_key?(PLATFORMS)
|
448
|
+
@dependencies = handle_deprecated_constraints(o[DEPENDENCIES]) if o.has_key?(DEPENDENCIES)
|
449
|
+
@recommendations = handle_deprecated_constraints(o[RECOMMENDATIONS]) if o.has_key?(RECOMMENDATIONS)
|
450
|
+
@suggestions = handle_deprecated_constraints(o[SUGGESTIONS]) if o.has_key?(SUGGESTIONS)
|
451
|
+
@conflicting = handle_deprecated_constraints(o[CONFLICTING]) if o.has_key?(CONFLICTING)
|
452
|
+
@providing = o[PROVIDING] if o.has_key?(PROVIDING)
|
453
|
+
@replacing = handle_deprecated_constraints(o[REPLACING]) if o.has_key?(REPLACING)
|
454
|
+
@attributes = o[ATTRIBUTES] if o.has_key?(ATTRIBUTES)
|
455
|
+
@groupings = o[GROUPINGS] if o.has_key?(GROUPINGS)
|
456
|
+
@recipes = o[RECIPES] if o.has_key?(RECIPES)
|
457
|
+
@version = o[VERSION] if o.has_key?(VERSION)
|
458
|
+
self
|
459
|
+
end
|
460
|
+
|
461
|
+
private
|
462
|
+
|
463
|
+
# Verify that the given array is an array of strings
|
464
|
+
#
|
465
|
+
# Raise an exception if the members of the array are not Strings
|
466
|
+
#
|
467
|
+
# === Parameters
|
468
|
+
# arry<Array>:: An array to be validated
|
469
|
+
def validate_string_array(arry)
|
470
|
+
if arry.kind_of?(Array)
|
471
|
+
arry.each do |choice|
|
472
|
+
validate( {:choice => choice}, {:choice => {:kind_of => String}} )
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# For backwards compatibility, remap Boolean values to String
|
478
|
+
# true is mapped to "required"
|
479
|
+
# false is mapped to "optional"
|
480
|
+
#
|
481
|
+
# === Parameters
|
482
|
+
# required_attr<String><Boolean>:: The value of options[:required]
|
483
|
+
#
|
484
|
+
# === Returns
|
485
|
+
# required_attr<String>:: "required", "recommended", or "optional"
|
486
|
+
def remap_required_attribute(value)
|
487
|
+
case value
|
488
|
+
when true
|
489
|
+
value = "required"
|
490
|
+
when false
|
491
|
+
value = "optional"
|
492
|
+
end
|
493
|
+
value
|
494
|
+
end
|
495
|
+
|
496
|
+
def validate_calculated_default_rule(options)
|
497
|
+
calculated_conflict = ((options[:default].is_a?(Array) && !options[:default].empty?) ||
|
498
|
+
(options[:default].is_a?(String) && !options[:default] != "")) &&
|
499
|
+
options[:calculated] == true
|
500
|
+
raise ArgumentError, "Default cannot be specified if calculated is true!" if calculated_conflict
|
501
|
+
end
|
502
|
+
|
503
|
+
def validate_choice_default_rule(options)
|
504
|
+
return if !options[:choice].is_a?(Array) || options[:choice].empty?
|
505
|
+
|
506
|
+
if options[:default].is_a?(String) && options[:default] != ""
|
507
|
+
raise ArgumentError, "Default must be one of your choice values!" if options[:choice].index(options[:default]) == nil
|
508
|
+
end
|
509
|
+
|
510
|
+
if options[:default].is_a?(Array) && !options[:default].empty?
|
511
|
+
options[:default].each do |val|
|
512
|
+
raise ArgumentError, "Default values must be a subset of your choice values!" if options[:choice].index(val) == nil
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# This method translates version constraint strings from
|
518
|
+
# cookbooks with the old format.
|
519
|
+
#
|
520
|
+
# Before we began respecting version constraints, we allowed
|
521
|
+
# multiple constraints to be placed on cookbooks, as well as the
|
522
|
+
# << and >> operators, which are now just < and >. For
|
523
|
+
# specifications with more than one constraint, we return an
|
524
|
+
# empty array (otherwise, we're silently abiding only part of
|
525
|
+
# the contract they have specified to us). If there is only one
|
526
|
+
# constraint, we are replacing the old << and >> with the new <
|
527
|
+
# and >.
|
528
|
+
def handle_deprecated_constraints(specification)
|
529
|
+
specification.inject(Hashie::Mash.new) do |acc, (cb, constraints)|
|
530
|
+
constraints = Array(constraints)
|
531
|
+
acc[cb] = (constraints.empty? || constraints.size > 1) ? [] : constraints.first.gsub(/>>/, '>').gsub(/<</, '<')
|
532
|
+
acc
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
#== Chef::Cookbook::MinimalMetadata
|
538
|
+
# MinimalMetadata is a duck type of Cookbook::Metadata, used
|
539
|
+
# internally by Chef Server when determining the optimal set of
|
540
|
+
# cookbooks for a node.
|
541
|
+
#
|
542
|
+
# MinimalMetadata objects typically contain only enough information
|
543
|
+
# to solve the cookbook collection for a run list, but not enough to
|
544
|
+
# generate the proper response
|
545
|
+
class MinimalMetadata < Metadata
|
546
|
+
def initialize(name, params)
|
547
|
+
@name = name
|
548
|
+
from_hash(params)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|