puppet 3.5.0.rc3 → 3.5.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/lib/puppet.rb +11 -2
- data/lib/puppet/defaults.rb +3 -2
- data/lib/puppet/environments.rb +16 -0
- data/lib/puppet/network/http/api/v1.rb +6 -2
- data/lib/puppet/network/http/issues.rb +1 -0
- data/lib/puppet/node/environment.rb +9 -5
- data/lib/puppet/pops/parser/interpolation_support.rb +8 -4
- data/lib/puppet/provider/yumrepo/inifile.rb +60 -20
- data/lib/puppet/test/test_helper.rb +1 -8
- data/lib/puppet/type/yumrepo.rb +43 -9
- data/lib/puppet/util/inifile.rb +209 -86
- data/lib/puppet/version.rb +1 -1
- data/spec/integration/application/apply_spec.rb +3 -1
- data/spec/integration/directory_environments_spec.rb +2 -1
- data/spec/integration/indirector/file_content/file_server_spec.rb +1 -1
- data/spec/integration/node/environment_spec.rb +2 -2
- data/spec/integration/resource/type_collection_spec.rb +1 -1
- data/spec/unit/application/apply_spec.rb +1 -1
- data/spec/unit/environments_spec.rb +35 -36
- data/spec/unit/face/module/install_spec.rb +1 -1
- data/spec/unit/face/module/list_spec.rb +3 -3
- data/spec/unit/face/module/uninstall_spec.rb +1 -1
- data/spec/unit/face/parser_spec.rb +1 -1
- data/spec/unit/indirector/node/active_record_spec.rb +1 -1
- data/spec/unit/indirector/node/ldap_spec.rb +4 -4
- data/spec/unit/indirector/node/plain_spec.rb +1 -1
- data/spec/unit/indirector/request_spec.rb +3 -3
- data/spec/unit/module_spec.rb +6 -6
- data/spec/unit/module_tool/applications/installer_spec.rb +1 -1
- data/spec/unit/module_tool/applications/uninstaller_spec.rb +1 -1
- data/spec/unit/module_tool_spec.rb +1 -1
- data/spec/unit/network/http/api/v1_spec.rb +11 -1
- data/spec/unit/network/http/api/v2/environments_spec.rb +1 -1
- data/spec/unit/node/environment_spec.rb +1 -1
- data/spec/unit/node_spec.rb +1 -1
- data/spec/unit/parser/ast/collection_spec.rb +1 -1
- data/spec/unit/parser/compiler_spec.rb +1 -1
- data/spec/unit/parser/files_spec.rb +2 -2
- data/spec/unit/parser/functions_spec.rb +2 -2
- data/spec/unit/parser/parser_spec.rb +2 -2
- data/spec/unit/parser/resource_spec.rb +1 -1
- data/spec/unit/parser/scope_spec.rb +3 -3
- data/spec/unit/pops/parser/lexer2_spec.rb +11 -0
- data/spec/unit/pops/parser/parse_heredoc_spec.rb +15 -1
- data/spec/unit/provider/yumrepo/inifile_spec.rb +71 -23
- data/spec/unit/rails/host_spec.rb +1 -1
- data/spec/unit/resource/type_collection_spec.rb +1 -1
- data/spec/unit/resource/type_spec.rb +1 -1
- data/spec/unit/resource_spec.rb +1 -1
- data/spec/unit/type/yumrepo_spec.rb +214 -49
- data/spec/unit/util/autoload_spec.rb +1 -1
- data/spec/unit/util/inifile_spec.rb +492 -0
- data/spec/unit/util/rdoc/parser_spec.rb +2 -2
- metadata +3009 -3030
data/lib/puppet.rb
CHANGED
@@ -177,8 +177,17 @@ module Puppet
|
|
177
177
|
environments = settings[:environmentpath]
|
178
178
|
modulepath = Puppet::Node::Environment.split_path(settings[:basemodulepath])
|
179
179
|
|
180
|
-
|
181
|
-
|
180
|
+
if environments.empty?
|
181
|
+
loaders = [Puppet::Environments::Legacy.new]
|
182
|
+
else
|
183
|
+
loaders = Puppet::Environments::Directories.from_path(environments, modulepath)
|
184
|
+
# in case the configured environment (used for the default sometimes)
|
185
|
+
# doesn't exist
|
186
|
+
loaders << Puppet::Environments::StaticPrivate.new(
|
187
|
+
Puppet::Node::Environment.create(Puppet[:environment].to_sym,
|
188
|
+
[],
|
189
|
+
Puppet::Node::Environment::NO_MANIFEST))
|
190
|
+
end
|
182
191
|
|
183
192
|
{
|
184
193
|
:environments => Puppet::Environments::Combined.new(*loaders)
|
data/lib/puppet/defaults.rb
CHANGED
@@ -197,8 +197,9 @@ module Puppet
|
|
197
197
|
this provides the default environment for nodes we know nothing about."
|
198
198
|
},
|
199
199
|
:environmentpath => {
|
200
|
-
:default => "
|
201
|
-
:desc => "A path
|
200
|
+
:default => "",
|
201
|
+
:desc => "A search path for environment directories, as a list of directories separated by the system
|
202
|
+
path separator character.",
|
202
203
|
:type => :path,
|
203
204
|
},
|
204
205
|
:diff_args => {
|
data/lib/puppet/environments.rb
CHANGED
@@ -60,6 +60,22 @@ module Puppet::Environments
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
# A source of unlisted pre-defined environments.
|
64
|
+
#
|
65
|
+
# Used only for internal bootstrapping environments which are not relevant
|
66
|
+
# to an end user (such as the fall back 'configured' environment).
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
class StaticPrivate < Static
|
70
|
+
|
71
|
+
# Unlisted
|
72
|
+
#
|
73
|
+
# @!macro loader_list
|
74
|
+
def list
|
75
|
+
[]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
63
79
|
# Old-style environments that come either from explicit stanzas in
|
64
80
|
# puppet.conf or from dynamic environments created from use of `$environment`
|
65
81
|
# in puppet.conf.
|
@@ -64,8 +64,12 @@ class Puppet::Network::HTTP::API::V1
|
|
64
64
|
method = indirection_method(http_method, indirection)
|
65
65
|
|
66
66
|
configured_environment = Puppet.lookup(:environments).get(environment)
|
67
|
-
|
68
|
-
|
67
|
+
if configured_environment.nil?
|
68
|
+
raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("Could not find #{environment}", Puppet::Network::HTTP::Issues::ENVIRONMENT_NOT_FOUND)
|
69
|
+
else
|
70
|
+
configured_environment = configured_environment.override_from_commandline(Puppet.settings)
|
71
|
+
params[:environment] = configured_environment
|
72
|
+
end
|
69
73
|
|
70
74
|
params.delete(:bucket_path)
|
71
75
|
|
@@ -2,6 +2,7 @@ module Puppet::Network::HTTP::Issues
|
|
2
2
|
NO_INDIRECTION_REMOTE_REQUESTS = :NO_INDIRECTION_REMOTE_REQUESTS
|
3
3
|
HANDLER_NOT_FOUND = :HANDLER_NOT_FOUND
|
4
4
|
RESOURCE_NOT_FOUND = :RESOURCE_NOT_FOUND
|
5
|
+
ENVIRONMENT_NOT_FOUND = :ENVIRONMENT_NOT_FOUND
|
5
6
|
RUNTIME_ERROR = :RUNTIME_ERROR
|
6
7
|
MISSING_HEADER_FIELD = :MISSING_HEADER_FIELD
|
7
8
|
UNSUPPORTED_FORMAT = :UNSUPPORTED_FORMAT
|
@@ -22,9 +22,10 @@ end
|
|
22
22
|
# logging functions. Logging functions are attached to the 'root' environment
|
23
23
|
# when {Puppet::Parser::Functions.reset} is called.
|
24
24
|
class Puppet::Node::Environment
|
25
|
-
|
26
25
|
include Puppet::Util::Cacher
|
27
26
|
|
27
|
+
NO_MANIFEST = :no_manifest
|
28
|
+
|
28
29
|
# @api private
|
29
30
|
def self.seen
|
30
31
|
@seen ||= {}
|
@@ -72,16 +73,17 @@ class Puppet::Node::Environment
|
|
72
73
|
#
|
73
74
|
# @param name [Symbol] the name of the
|
74
75
|
# @param modulepath [Array<String>] the list of paths from which to load modules
|
75
|
-
# @param manifest [String] the path to the manifest for the environment
|
76
|
+
# @param manifest [String] the path to the manifest for the environment or
|
77
|
+
# the constant Puppet::Node::Environment::NO_MANIFEST if there is none.
|
76
78
|
# @return [Puppet::Node::Environment]
|
77
79
|
#
|
78
80
|
# @api public
|
79
|
-
def self.create(name, modulepath, manifest)
|
81
|
+
def self.create(name, modulepath, manifest = NO_MANIFEST)
|
80
82
|
obj = self.allocate
|
81
83
|
obj.send(:initialize,
|
82
84
|
name,
|
83
85
|
expand_dirs(extralibs() + modulepath),
|
84
|
-
File.expand_path(manifest))
|
86
|
+
manifest == NO_MANIFEST ? manifest : File.expand_path(manifest))
|
85
87
|
obj
|
86
88
|
end
|
87
89
|
|
@@ -459,7 +461,9 @@ class Puppet::Node::Environment
|
|
459
461
|
file = self.manifest
|
460
462
|
# if the manifest file is a reference to a directory, parse and combine all .pp files in that
|
461
463
|
# directory
|
462
|
-
if
|
464
|
+
if file == NO_MANIFEST
|
465
|
+
Puppet::Parser::AST::Hostclass.new('')
|
466
|
+
elsif File.directory?(file)
|
463
467
|
parse_results = Dir.entries(file).find_all { |f| f =~ /\.pp$/ }.sort.map do |pp_file|
|
464
468
|
parser.file = File.join(file, pp_file)
|
465
469
|
parser.parse
|
@@ -44,8 +44,9 @@ module Puppet::Pops::Parser::InterpolationSupport
|
|
44
44
|
break
|
45
45
|
else
|
46
46
|
# false $ variable start
|
47
|
-
text +=
|
47
|
+
text += terminator
|
48
48
|
value,terminator = slurp_dqstring()
|
49
|
+
text += value
|
49
50
|
after = scn.pos
|
50
51
|
end
|
51
52
|
end
|
@@ -84,8 +85,9 @@ module Puppet::Pops::Parser::InterpolationSupport
|
|
84
85
|
break
|
85
86
|
else
|
86
87
|
# false $ variable start
|
88
|
+
text += terminator
|
89
|
+
value,terminator = slurp_dqstring
|
87
90
|
text += value
|
88
|
-
value,terminator = self.send(slurpfunc)
|
89
91
|
after = scn.pos
|
90
92
|
end
|
91
93
|
end
|
@@ -127,8 +129,9 @@ module Puppet::Pops::Parser::InterpolationSupport
|
|
127
129
|
break
|
128
130
|
else
|
129
131
|
# false $ variable start
|
130
|
-
text +=
|
132
|
+
text += terminator
|
131
133
|
value,terminator = slurp_uqstring()
|
134
|
+
text += value
|
132
135
|
after = scn.pos
|
133
136
|
end
|
134
137
|
end
|
@@ -166,8 +169,9 @@ module Puppet::Pops::Parser::InterpolationSupport
|
|
166
169
|
break
|
167
170
|
else
|
168
171
|
# false $ variable start
|
169
|
-
text +=
|
172
|
+
text += terminator
|
170
173
|
value,terminator = slurp_uqstring
|
174
|
+
text += value
|
171
175
|
after = scn.pos
|
172
176
|
end
|
173
177
|
end
|
@@ -18,7 +18,9 @@ Puppet::Type.type(:yumrepo).provide(:inifile) do
|
|
18
18
|
if valid_property?(key)
|
19
19
|
# We strip the values here to handle cases where distros set values
|
20
20
|
# like enabled = 1 with spaces.
|
21
|
-
attributes_hash[key] = value
|
21
|
+
attributes_hash[key] = value
|
22
|
+
elsif key == :name
|
23
|
+
attributes_hash[:descr] = value
|
22
24
|
end
|
23
25
|
end
|
24
26
|
instances << new(attributes_hash)
|
@@ -132,7 +134,7 @@ Puppet::Type.type(:yumrepo).provide(:inifile) do
|
|
132
134
|
def create
|
133
135
|
@property_hash[:ensure] = :present
|
134
136
|
|
135
|
-
new_section =
|
137
|
+
new_section = current_section
|
136
138
|
|
137
139
|
# We fetch a list of properties from the type, then iterate
|
138
140
|
# over them, avoiding ensure. We're relying on .should to
|
@@ -140,19 +142,25 @@ Puppet::Type.type(:yumrepo).provide(:inifile) do
|
|
140
142
|
# and if so we set it in the virtual inifile.
|
141
143
|
PROPERTIES.each do |property|
|
142
144
|
next if property == :ensure
|
145
|
+
|
146
|
+
|
143
147
|
if value = @resource.should(property)
|
144
|
-
|
145
|
-
@property_hash[property] = value
|
148
|
+
self.send("#{property}=", value)
|
146
149
|
end
|
147
150
|
end
|
148
151
|
end
|
149
152
|
|
153
|
+
# @return [Boolean] Returns true if ensure => present.
|
154
|
+
def exists?
|
155
|
+
@property_hash[:ensure] == :present
|
156
|
+
end
|
157
|
+
|
150
158
|
# We don't actually destroy the file here, merely mark it for
|
151
159
|
# destruction in the section.
|
152
160
|
# @return [void]
|
153
161
|
def destroy
|
154
162
|
# Flag file for deletion on flush.
|
155
|
-
|
163
|
+
current_section.destroy=(true)
|
156
164
|
|
157
165
|
@property_hash.clear
|
158
166
|
end
|
@@ -162,26 +170,58 @@ Puppet::Type.type(:yumrepo).provide(:inifile) do
|
|
162
170
|
self.class.store
|
163
171
|
end
|
164
172
|
|
165
|
-
#
|
166
|
-
def section(name)
|
167
|
-
self.class.section(name)
|
168
|
-
end
|
169
|
-
|
170
|
-
# Create all of our setters.
|
171
|
-
mk_resource_methods
|
173
|
+
# Generate setters and getters for our INI properties.
|
172
174
|
PROPERTIES.each do |property|
|
173
|
-
#
|
175
|
+
# The ensure property uses #create, #exists, and #destroy we can't generate
|
176
|
+
# meaningful setters and getters for this
|
174
177
|
next if property == :ensure
|
175
|
-
|
176
|
-
define_method(
|
177
|
-
|
178
|
-
|
178
|
+
|
179
|
+
define_method(property) do
|
180
|
+
get_property(property)
|
181
|
+
end
|
182
|
+
|
183
|
+
define_method("#{property}=") do |value|
|
184
|
+
set_property(property, value)
|
179
185
|
end
|
180
186
|
end
|
181
187
|
|
182
|
-
#
|
183
|
-
def
|
184
|
-
@property_hash
|
188
|
+
# Map the yumrepo 'descr' type property to the 'name' INI property.
|
189
|
+
def descr
|
190
|
+
if ! @property_hash.has_key?(:descr)
|
191
|
+
@property_hash[:descr] = current_section['name']
|
192
|
+
end
|
193
|
+
value = @property_hash[:descr]
|
194
|
+
value.nil? ? :absent : value
|
195
|
+
end
|
196
|
+
|
197
|
+
def descr=(value)
|
198
|
+
value = (value == :absent ? nil : value)
|
199
|
+
current_section['name'] = value
|
200
|
+
@property_hash[:descr] = value
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def get_property(property)
|
206
|
+
if ! @property_hash.has_key?(property)
|
207
|
+
@property_hash[property] = current_section[property.to_s]
|
208
|
+
end
|
209
|
+
value = @property_hash[property]
|
210
|
+
value.nil? ? :absent : value
|
185
211
|
end
|
186
212
|
|
213
|
+
def set_property(property, value)
|
214
|
+
value = (value == :absent ? nil : value)
|
215
|
+
current_section[property.to_s] = value
|
216
|
+
@property_hash[property] = value
|
217
|
+
end
|
218
|
+
|
219
|
+
# @return [void]
|
220
|
+
def section(name)
|
221
|
+
self.class.section(name)
|
222
|
+
end
|
223
|
+
|
224
|
+
def current_section
|
225
|
+
self.class.section(self.name)
|
226
|
+
end
|
187
227
|
end
|
@@ -37,19 +37,12 @@ module Puppet::Test
|
|
37
37
|
# @return nil
|
38
38
|
def self.initialize()
|
39
39
|
owner = Process.pid
|
40
|
-
@environmentdir = Dir.mktmpdir('environments')
|
41
40
|
Puppet.push_context(Puppet.base_context({
|
42
|
-
:environmentpath =>
|
41
|
+
:environmentpath => "",
|
43
42
|
:basemodulepath => "",
|
44
43
|
:manifest => "/dev/null"
|
45
44
|
}), "Initial for specs")
|
46
45
|
Puppet::Parser::Functions.reset
|
47
|
-
|
48
|
-
ObjectSpace.define_finalizer(Puppet.lookup(:environments), proc {
|
49
|
-
if Process.pid == owner
|
50
|
-
FileUtils.rm_rf(@environmentdir)
|
51
|
-
end
|
52
|
-
})
|
53
46
|
end
|
54
47
|
|
55
48
|
# Call this method once, when beginning a test run--prior to running
|
data/lib/puppet/type/yumrepo.rb
CHANGED
@@ -18,9 +18,11 @@ Puppet::Type.newtype(:yumrepo) do
|
|
18
18
|
# Doc string for properties that can be made 'absent'
|
19
19
|
ABSENT_DOC="Set this to `absent` to remove it from the file completely."
|
20
20
|
# False can be false/0/no and True can be true/1/yes in yum.
|
21
|
-
YUM_BOOLEAN=/(True|False|0|1|No|Yes)/
|
21
|
+
YUM_BOOLEAN=/(True|False|0|1|No|Yes)/i
|
22
22
|
YUM_BOOLEAN_DOC="Valid values are: False/0/No or True/1/Yes."
|
23
23
|
|
24
|
+
VALID_SCHEMES = %w[file http https ftp]
|
25
|
+
|
24
26
|
newparam(:name, :namevar => true) do
|
25
27
|
desc "The name of the repository. This corresponds to the
|
26
28
|
`repositoryid` parameter in `yum.conf(5)`."
|
@@ -46,8 +48,12 @@ Puppet::Type.newtype(:yumrepo) do
|
|
46
48
|
|
47
49
|
newvalues(/.*/, :absent)
|
48
50
|
validate do |value|
|
51
|
+
next if value.to_s == 'absent'
|
49
52
|
parsed = URI.parse(value)
|
50
|
-
|
53
|
+
|
54
|
+
unless VALID_SCHEMES.include?(parsed.scheme)
|
55
|
+
raise "Must be a valid URL"
|
56
|
+
end
|
51
57
|
end
|
52
58
|
end
|
53
59
|
|
@@ -56,8 +62,16 @@ Puppet::Type.newtype(:yumrepo) do
|
|
56
62
|
|
57
63
|
newvalues(/.*/, :absent)
|
58
64
|
validate do |value|
|
59
|
-
|
60
|
-
|
65
|
+
next if value.to_s == 'absent'
|
66
|
+
|
67
|
+
value.split(/\s+/).each do |uri|
|
68
|
+
|
69
|
+
parsed = URI.parse(uri)
|
70
|
+
|
71
|
+
unless VALID_SCHEMES.include?(parsed.scheme)
|
72
|
+
raise "Must be a valid URL"
|
73
|
+
end
|
74
|
+
end
|
61
75
|
end
|
62
76
|
end
|
63
77
|
|
@@ -92,8 +106,16 @@ Puppet::Type.newtype(:yumrepo) do
|
|
92
106
|
|
93
107
|
newvalues(/.*/, :absent)
|
94
108
|
validate do |value|
|
95
|
-
|
96
|
-
|
109
|
+
next if value.to_s == 'absent'
|
110
|
+
|
111
|
+
value.split(/\s+/).each do |uri|
|
112
|
+
|
113
|
+
parsed = URI.parse(uri)
|
114
|
+
|
115
|
+
unless VALID_SCHEMES.include?(parsed.scheme)
|
116
|
+
raise "Must be a valid URL"
|
117
|
+
end
|
118
|
+
end
|
97
119
|
end
|
98
120
|
end
|
99
121
|
|
@@ -104,8 +126,12 @@ Puppet::Type.newtype(:yumrepo) do
|
|
104
126
|
|
105
127
|
newvalues(/.*/, :absent)
|
106
128
|
validate do |value|
|
129
|
+
next if value.to_s == 'absent'
|
107
130
|
parsed = URI.parse(value)
|
108
|
-
|
131
|
+
|
132
|
+
unless VALID_SCHEMES.include?(parsed.scheme)
|
133
|
+
raise "Must be a valid URL"
|
134
|
+
end
|
109
135
|
end
|
110
136
|
end
|
111
137
|
|
@@ -202,8 +228,12 @@ Puppet::Type.newtype(:yumrepo) do
|
|
202
228
|
|
203
229
|
newvalues(/.*/, :absent)
|
204
230
|
validate do |value|
|
231
|
+
next if value.to_s == 'absent'
|
205
232
|
parsed = URI.parse(value)
|
206
|
-
|
233
|
+
|
234
|
+
unless VALID_SCHEMES.include?(parsed.scheme)
|
235
|
+
raise "Must be a valid URL"
|
236
|
+
end
|
207
237
|
end
|
208
238
|
end
|
209
239
|
|
@@ -262,8 +292,12 @@ Puppet::Type.newtype(:yumrepo) do
|
|
262
292
|
|
263
293
|
newvalues(/.*/, :absent)
|
264
294
|
validate do |value|
|
295
|
+
next if value.to_s == 'absent'
|
265
296
|
parsed = URI.parse(value)
|
266
|
-
|
297
|
+
|
298
|
+
unless VALID_SCHEMES.include?(parsed.scheme)
|
299
|
+
raise "Must be a valid URL"
|
300
|
+
end
|
267
301
|
end
|
268
302
|
end
|
269
303
|
|
data/lib/puppet/util/inifile.rb
CHANGED
@@ -9,6 +9,7 @@
|
|
9
9
|
# The parsing tries to stay close to python's ConfigParser
|
10
10
|
|
11
11
|
require 'puppet/util/filetype'
|
12
|
+
require 'puppet/error'
|
12
13
|
|
13
14
|
module Puppet::Util::IniConfig
|
14
15
|
# A section in a .ini file
|
@@ -24,10 +25,17 @@ module Puppet::Util::IniConfig
|
|
24
25
|
@destroy = false
|
25
26
|
end
|
26
27
|
|
27
|
-
#
|
28
|
-
#
|
28
|
+
# Does this section need to be updated in/removed from the associated file?
|
29
|
+
#
|
30
|
+
# @note This section is dirty if a key has been modified _or_ if the
|
31
|
+
# section has been modified so the associated file can be rewritten
|
32
|
+
# without this section.
|
29
33
|
def dirty?
|
30
|
-
@dirty
|
34
|
+
@dirty or @destroy
|
35
|
+
end
|
36
|
+
|
37
|
+
def mark_dirty
|
38
|
+
@dirty = true
|
31
39
|
end
|
32
40
|
|
33
41
|
# Should only be used internally
|
@@ -69,13 +77,17 @@ module Puppet::Util::IniConfig
|
|
69
77
|
# Format the section as text in the way it should be
|
70
78
|
# written to file
|
71
79
|
def format
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
80
|
+
if @destroy
|
81
|
+
text = ""
|
82
|
+
else
|
83
|
+
text = "[#{name}]\n"
|
84
|
+
@entries.each do |entry|
|
85
|
+
if entry.is_a?(Array)
|
86
|
+
key, value = entry
|
87
|
+
text << "#{key}=#{value}\n" unless value.nil?
|
88
|
+
else
|
89
|
+
text << entry
|
90
|
+
end
|
79
91
|
end
|
80
92
|
end
|
81
93
|
text
|
@@ -91,128 +103,239 @@ module Puppet::Util::IniConfig
|
|
91
103
|
|
92
104
|
end
|
93
105
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
106
|
+
class PhysicalFile
|
107
|
+
|
108
|
+
# @!attribute [r] filetype
|
109
|
+
# @api private
|
110
|
+
# @return [Puppet::Util::FileType::FileTypeFlat]
|
111
|
+
attr_reader :filetype
|
112
|
+
|
113
|
+
# @!attribute [r] contents
|
114
|
+
# @api private
|
115
|
+
# @return [Array<String, Puppet::Util::IniConfig::Section>]
|
116
|
+
attr_reader :contents
|
117
|
+
|
118
|
+
# @!attribute [rw] destroy_empty
|
119
|
+
# Whether empty files should be removed if no sections are defined.
|
120
|
+
# Defaults to false
|
121
|
+
attr_accessor :destroy_empty
|
122
|
+
|
123
|
+
# @!attribute [rw] file_collection
|
124
|
+
# @return [Puppet::Util::IniConfig::FileCollection]
|
125
|
+
attr_accessor :file_collection
|
126
|
+
|
127
|
+
def initialize(file, options = {})
|
128
|
+
@file = file
|
129
|
+
@contents = []
|
130
|
+
@filetype = Puppet::Util::FileType.filetype(:flat).new(file)
|
131
|
+
|
132
|
+
@destroy_empty = options.fetch(:destroy_empty, false)
|
99
133
|
end
|
100
134
|
|
101
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
text
|
105
|
-
|
135
|
+
# Read and parse the on-disk file associated with this object
|
136
|
+
def read
|
137
|
+
text = @filetype.read
|
138
|
+
if text.nil?
|
139
|
+
raise IniParseError, "Cannot read nonexistent file #{@file.inspect}"
|
140
|
+
end
|
141
|
+
parse(text)
|
142
|
+
end
|
143
|
+
|
144
|
+
INI_COMMENT = Regexp.union(
|
145
|
+
/^\s*$/,
|
146
|
+
/^[#;]/,
|
147
|
+
/^\s*rem\s/i
|
148
|
+
)
|
149
|
+
INI_CONTINUATION = /^[ \t\r\n\f]/
|
150
|
+
INI_SECTION_NAME = /^\[([^\]]+)\]/
|
151
|
+
INI_PROPERTY = /^\s*([^\s=]+)\s*\=(.*)$/
|
106
152
|
|
153
|
+
# @api private
|
154
|
+
def parse(text)
|
107
155
|
section = nil # The name of the current section
|
108
156
|
optname = nil # The name of the last option in section
|
109
|
-
|
110
|
-
|
157
|
+
line_num = 0
|
158
|
+
|
111
159
|
text.each_line do |l|
|
112
|
-
|
113
|
-
if l.
|
114
|
-
(l.split(nil, 2)[0].downcase == "rem" && l[0,1].downcase == "r")
|
160
|
+
line_num += 1
|
161
|
+
if l.match(INI_COMMENT)
|
115
162
|
# Whitespace or comment
|
116
163
|
if section.nil?
|
117
|
-
@
|
164
|
+
@contents << l
|
118
165
|
else
|
119
166
|
section.add_line(l)
|
120
167
|
end
|
121
|
-
elsif
|
168
|
+
elsif l.match(INI_CONTINUATION) && section && optname
|
122
169
|
# continuation line
|
123
170
|
section[optname] += "\n#{l.chomp}"
|
124
|
-
elsif
|
171
|
+
elsif (match = l.match(INI_SECTION_NAME))
|
125
172
|
# section heading
|
126
|
-
section.mark_clean
|
127
|
-
|
173
|
+
section.mark_clean if section
|
174
|
+
|
175
|
+
section_name = match[1]
|
176
|
+
|
177
|
+
section = add_section(section_name)
|
128
178
|
optname = nil
|
129
|
-
elsif
|
179
|
+
elsif (match = l.match(INI_PROPERTY))
|
130
180
|
# We allow space around the keys, but not the values
|
131
181
|
# For the values, we don't know if space is significant
|
182
|
+
key = match[1]
|
183
|
+
val = match[2]
|
184
|
+
|
132
185
|
if section.nil?
|
133
|
-
raise "#{
|
134
|
-
else
|
135
|
-
section[$1] = $2
|
136
|
-
optname = $1
|
186
|
+
raise IniParseError.new("Property with key #{key.inspect} outside of a section")
|
137
187
|
end
|
188
|
+
|
189
|
+
section[key] = val
|
190
|
+
optname = key
|
138
191
|
else
|
139
|
-
raise "
|
192
|
+
raise IniParseError.new("Can't parse line '#{l.chomp}'", @file, line_num)
|
140
193
|
end
|
141
194
|
end
|
142
195
|
section.mark_clean unless section.nil?
|
143
196
|
end
|
144
197
|
|
145
|
-
#
|
146
|
-
#
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
# We delete the file and then remove it from the list of files.
|
164
|
-
if destroy
|
165
|
-
::File.unlink(file)
|
166
|
-
@files.delete(file)
|
198
|
+
# @return [Array<Puppet::Util::IniConfig::Section>] All sections defined in
|
199
|
+
# this file.
|
200
|
+
def sections
|
201
|
+
@contents.select { |entry| entry.is_a? Section }
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [Puppet::Util::IniConfig::Section, nil] The section with the
|
205
|
+
# given name if it exists, else nil.
|
206
|
+
def get_section(name)
|
207
|
+
@contents.find { |entry| entry.is_a? Section and entry.name == name }
|
208
|
+
end
|
209
|
+
|
210
|
+
def format
|
211
|
+
text = ""
|
212
|
+
|
213
|
+
@contents.each do |content|
|
214
|
+
if content.is_a? Section
|
215
|
+
text << content.format
|
167
216
|
else
|
168
|
-
|
169
|
-
Puppet::Util::FileType.filetype(:flat).new(file).write(text)
|
170
|
-
return file
|
171
|
-
end
|
217
|
+
text << content
|
172
218
|
end
|
173
219
|
end
|
220
|
+
|
221
|
+
text
|
222
|
+
end
|
223
|
+
|
224
|
+
def store
|
225
|
+
if @destroy_empty and (sections.empty? or sections.all?(&:destroy?))
|
226
|
+
::File.unlink(@file)
|
227
|
+
elsif sections.any?(&:dirty?)
|
228
|
+
text = self.format
|
229
|
+
@filetype.write(text)
|
230
|
+
end
|
231
|
+
sections.each(&:mark_clean)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Create a new section and store it in the file contents
|
235
|
+
#
|
236
|
+
# @api private
|
237
|
+
# @param name [String] The name of the section to create
|
238
|
+
# @return [Puppet::Util::IniConfig::Section]
|
239
|
+
def add_section(name)
|
240
|
+
if section_exists?(name)
|
241
|
+
raise IniParseError.new("Section #{name.inspect} is already defined, cannot redefine", @file)
|
242
|
+
end
|
243
|
+
|
244
|
+
section = Section.new(name, @file)
|
245
|
+
@contents << section
|
246
|
+
|
247
|
+
section
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
def section_exists?(name)
|
253
|
+
if self.get_section(name)
|
254
|
+
true
|
255
|
+
elsif @file_collection and @file_collection.get_section(name)
|
256
|
+
true
|
257
|
+
else
|
258
|
+
false
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class FileCollection
|
264
|
+
|
265
|
+
attr_reader :files
|
266
|
+
|
267
|
+
def initialize
|
268
|
+
@files = {}
|
269
|
+
end
|
270
|
+
|
271
|
+
# Read and parse a file and store it in the collection. If the file has
|
272
|
+
# already been read it will be destroyed and re-read.
|
273
|
+
def read(file)
|
274
|
+
new_physical_file(file).read
|
275
|
+
end
|
276
|
+
|
277
|
+
def store
|
278
|
+
@files.values.each do |file|
|
279
|
+
file.store
|
280
|
+
end
|
174
281
|
end
|
175
282
|
|
176
|
-
# Execute BLOCK, passing each section in this file
|
177
|
-
# as an argument
|
178
283
|
def each_section(&block)
|
179
|
-
@files.each do |file
|
180
|
-
|
181
|
-
yield
|
284
|
+
@files.values.each do |file|
|
285
|
+
file.sections.each do |section|
|
286
|
+
yield section
|
182
287
|
end
|
183
288
|
end
|
184
289
|
end
|
185
290
|
|
186
|
-
# Execute BLOCK, passing each file constituting this inifile
|
187
|
-
# as an argument
|
188
291
|
def each_file(&block)
|
189
|
-
@files.keys.each do |
|
190
|
-
yield
|
292
|
+
@files.keys.each do |path|
|
293
|
+
yield path
|
191
294
|
end
|
192
295
|
end
|
193
296
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
297
|
+
def get_section(name)
|
298
|
+
sect = nil
|
299
|
+
@files.values.each do |file|
|
300
|
+
if (current = file.get_section(name))
|
301
|
+
sect = current
|
302
|
+
end
|
199
303
|
end
|
200
|
-
|
304
|
+
sect
|
201
305
|
end
|
306
|
+
alias [] get_section
|
202
307
|
|
203
|
-
# Return true if the file contains a section with name NAME
|
204
308
|
def include?(name)
|
205
|
-
|
309
|
+
!! get_section(name)
|
206
310
|
end
|
207
311
|
|
208
|
-
# Add a section to be stored in FILE when store is called
|
209
312
|
def add_section(name, file)
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
313
|
+
get_physical_file(file).add_section(name)
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
# Return a file if it's already been defined, create a new file if it hasn't
|
319
|
+
# been defined.
|
320
|
+
def get_physical_file(file)
|
321
|
+
if @files[file]
|
322
|
+
@files[file]
|
323
|
+
else
|
324
|
+
new_physical_file(file)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# Create a new physical file and set required attributes on that file.
|
329
|
+
def new_physical_file(file)
|
330
|
+
@files[file] = PhysicalFile.new(file)
|
331
|
+
@files[file].file_collection = self
|
332
|
+
@files[file]
|
215
333
|
end
|
216
334
|
end
|
217
|
-
end
|
218
335
|
|
336
|
+
File = FileCollection
|
337
|
+
|
338
|
+
class IniParseError < Puppet::Error
|
339
|
+
include Puppet::ExternalFileError
|
340
|
+
end
|
341
|
+
end
|