ridley 0.7.0.rc4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|