line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Copyright (c) 2011 Raphaƫl Pinson. |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# This library is free software; you can redistribute it and/or |
4
|
|
|
|
|
|
|
# modify it under the terms of the GNU Lesser Public License as |
5
|
|
|
|
|
|
|
# published by the Free Software Foundation; either version 2.1 of |
6
|
|
|
|
|
|
|
# the License, or (at your option) any later version. |
7
|
|
|
|
|
|
|
# |
8
|
|
|
|
|
|
|
# Config-Model is distributed in the hope that it will be useful, |
9
|
|
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
10
|
|
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11
|
|
|
|
|
|
|
# Lesser Public License for more details. |
12
|
|
|
|
|
|
|
# |
13
|
|
|
|
|
|
|
# You should have received a copy of the GNU Lesser Public License |
14
|
|
|
|
|
|
|
# along with Config-Model; if not, write to the Free Software |
15
|
|
|
|
|
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
16
|
|
|
|
|
|
|
# 02110-1301 USA |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
package Config::Augeas::Exporter; |
19
|
2
|
|
|
2
|
|
29752
|
use strict; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
82
|
|
20
|
2
|
|
|
2
|
|
12
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
74
|
|
21
|
2
|
|
|
2
|
|
9
|
use base qw(Class::Accessor); |
|
2
|
|
|
|
|
13
|
|
|
2
|
|
|
|
|
2076
|
|
22
|
|
|
|
|
|
|
|
23
|
2
|
|
|
2
|
|
8123
|
use Config::Augeas qw(get match count_match); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
use XML::LibXML; |
25
|
|
|
|
|
|
|
use Encode qw(encode); |
26
|
|
|
|
|
|
|
use YAML qw(Dump); |
27
|
|
|
|
|
|
|
use JSON qw(); |
28
|
|
|
|
|
|
|
use File::Path qw(mkpath); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw(to_xml to_hash to_yaml to_json from_xml)); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
our $VERSION = '1.0.0'; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# Default values |
35
|
|
|
|
|
|
|
my $PATH = '/files'; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=head1 NAME |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
Config::Augeas::Exporter - Export the Augeas tree to various formats |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 SYNOPSIS |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
use Config::Augeas::Exporter |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Initiliaze |
47
|
|
|
|
|
|
|
my $aug = Config::Augeas::Exporter->new( root => $aug_root ); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# Export to XML |
50
|
|
|
|
|
|
|
my $doc = $aug->to_xml( |
51
|
|
|
|
|
|
|
path => ['/files/etc/fstab', '/files/etc/apt'], |
52
|
|
|
|
|
|
|
exclude => ['#comment', '#mcomment'], |
53
|
|
|
|
|
|
|
file_stat => 1, |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
print $doc->toString; |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# Restore from XML |
59
|
|
|
|
|
|
|
open (my $fh, "<$file") |
60
|
|
|
|
|
|
|
or die "E: Could not open $file: $!\n" ; |
61
|
|
|
|
|
|
|
my $doc = XML::LibXML->load_xml(IO => $fh); |
62
|
|
|
|
|
|
|
close $fh; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
my $aug = Config::Augeas::Exporter->new(root => $root); |
65
|
|
|
|
|
|
|
$aug->from_xml( |
66
|
|
|
|
|
|
|
xml => $doc, |
67
|
|
|
|
|
|
|
create_dirs => 1, |
68
|
|
|
|
|
|
|
); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head1 DESCRIPTION |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
This module allows to export the Augeas tree to various formats and import back from these formats to the configuration files. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=head1 Constructor |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head1 new ( ... ) |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Creates a new Config::Augeas::Exporter object. Optional parameters are: |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=over |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=item augeas |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
A Config::Augeas object. If not provided, a new one will be created. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=item root |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
Use C as the filesystem root. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=back |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=cut |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub new { |
97
|
|
|
|
|
|
|
my $class = shift; |
98
|
|
|
|
|
|
|
my %options = @_; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
my $root = $options{root}; |
101
|
|
|
|
|
|
|
$root ||= ''; |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
$class = ref $class || $class || __PACKAGE__; |
104
|
|
|
|
|
|
|
my $self = __PACKAGE__->SUPER::new(); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Initiliaze Augeas if it wasn't passed |
107
|
|
|
|
|
|
|
my $aug = $options{augeas}; |
108
|
|
|
|
|
|
|
unless($aug) { |
109
|
|
|
|
|
|
|
$aug = Config::Augeas->new(root => $root); |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# Associate to object |
113
|
|
|
|
|
|
|
$self->{aug} = $aug; |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# Get augeas root |
116
|
|
|
|
|
|
|
$self->{aug_root} = $aug->get('/augeas/root'); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
return $self; |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head1 Methods |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head2 to_xml( ... ) |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Export the Augeas tree to a XML::LibXML::Document object. |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=over |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=item path |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
An array of Augeas paths to export. If ommitted, it will default to '/files'. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item exclude |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
An array of label patterns to exclude from the export. |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=item file_stat |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
A boolean, whether to include file stat. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=back |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=cut |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
sub to_xml { |
148
|
|
|
|
|
|
|
my $self = shift; |
149
|
|
|
|
|
|
|
my %args = @_; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
my @paths = @{$args{path}} if $args{path}; |
152
|
|
|
|
|
|
|
my @excludes = @{$args{exclude}} if $args{exclude}; |
153
|
|
|
|
|
|
|
my $file_stat = $args{file_stat}; |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# Defaults |
156
|
|
|
|
|
|
|
@paths = $PATH if ($#paths < 0); |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# Initialize XML document |
159
|
|
|
|
|
|
|
my $doc = XML::LibXML::Document->new('1.0', 'utf-8'); |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Get XML elements from augeas recursively |
162
|
|
|
|
|
|
|
my @file_elems; |
163
|
|
|
|
|
|
|
for my $path (@paths) { |
164
|
|
|
|
|
|
|
my @new_file_elems = (node_to_xml($self, $path, \@excludes, 1, $file_stat)); |
165
|
|
|
|
|
|
|
map { push @file_elems, $_ } @new_file_elems; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# Add a files node for all file entries |
169
|
|
|
|
|
|
|
my $files = XML::LibXML::Element->new('files'); |
170
|
|
|
|
|
|
|
map { $files->appendChild($_) } @file_elems; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# Raise warning if no "file" node was found |
173
|
|
|
|
|
|
|
my @node_nodes = $files->findnodes("//node"); |
174
|
|
|
|
|
|
|
my @file_nodes = $files->findnodes("//file"); |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
if ($#node_nodes >= 0 && $#file_nodes < 0) { |
177
|
|
|
|
|
|
|
warn "W: The XML export contains no file nodes. |
178
|
|
|
|
|
|
|
W: You will not be able to import it back.\n"; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# Add an error node for errors |
182
|
|
|
|
|
|
|
my @error_elems = (node_to_xml($self, '/augeas//error', [], 0, 0)); |
183
|
|
|
|
|
|
|
my $errors = XML::LibXML::Element->new('error'); |
184
|
|
|
|
|
|
|
map { $errors->appendChild($_) } @error_elems; |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# Add an augeas node on top |
187
|
|
|
|
|
|
|
my $augeas = XML::LibXML::Element->new('augeas'); |
188
|
|
|
|
|
|
|
$augeas->appendChild($files); |
189
|
|
|
|
|
|
|
$augeas->appendChild($errors); |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Associate files node with document |
192
|
|
|
|
|
|
|
$doc->setDocumentElement($augeas); |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
return $doc; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
sub node_to_xml { |
199
|
|
|
|
|
|
|
my ($self, $path, $excludes, $check_is_file, $incl_file_stat) = @_; |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Default check is_file |
202
|
|
|
|
|
|
|
$check_is_file = 1 unless(defined($check_is_file)); |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# Default incl_file_stat |
205
|
|
|
|
|
|
|
$incl_file_stat = 0 unless(defined($incl_file_stat)); |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Get label from path |
208
|
|
|
|
|
|
|
my $label = get_label($path); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Filter excludes |
211
|
|
|
|
|
|
|
return if exclude_match($label, $excludes); |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Sanitize path for augeas requests |
214
|
|
|
|
|
|
|
$path = sanitize_path($path); |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my $aug = $self->{aug}; |
217
|
|
|
|
|
|
|
my @children = $aug->match("$path/*"); |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# Should children check is_file? |
220
|
|
|
|
|
|
|
my $children_check_is_file = $check_is_file; |
221
|
|
|
|
|
|
|
if ($check_is_file && is_file($self, $path)) { |
222
|
|
|
|
|
|
|
$children_check_is_file = 0; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# Parse children |
226
|
|
|
|
|
|
|
my @child_elems; |
227
|
|
|
|
|
|
|
for my $child (@children) { |
228
|
|
|
|
|
|
|
my @new_child_elems = (node_to_xml($self, $child, $excludes, $children_check_is_file, $incl_file_stat)); |
229
|
|
|
|
|
|
|
map { push @child_elems, $_ } @new_child_elems; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Directories don't get their own node |
233
|
|
|
|
|
|
|
return @child_elems if ($check_is_file && $self->is_dir($path)); |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# Files and entries get their own nodes |
236
|
|
|
|
|
|
|
my $elem; |
237
|
|
|
|
|
|
|
if ($check_is_file && is_file($self, $path)) { |
238
|
|
|
|
|
|
|
# Files get nodes |
239
|
|
|
|
|
|
|
$elem = XML::LibXML::Element->new('file'); |
240
|
|
|
|
|
|
|
my $file_path = get_file_path($path); |
241
|
|
|
|
|
|
|
$elem->setAttribute("path", $file_path); |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# Include file stat if requested |
244
|
|
|
|
|
|
|
if ($incl_file_stat) { |
245
|
|
|
|
|
|
|
my $file_stat = $self->stat_to_xml($file_path); |
246
|
|
|
|
|
|
|
$elem->appendChild($file_stat); |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
} else { |
249
|
|
|
|
|
|
|
# Entries get nodes |
250
|
|
|
|
|
|
|
$elem = XML::LibXML::Element->new('node'); |
251
|
|
|
|
|
|
|
$elem->setAttribute("label", $label); |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# Append children to element |
255
|
|
|
|
|
|
|
map { $elem->appendChild($_) } @child_elems; |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
# Add value to element |
258
|
|
|
|
|
|
|
my $value = $aug->get($path); |
259
|
|
|
|
|
|
|
if (defined($value)) { |
260
|
|
|
|
|
|
|
my $value_elem = XML::LibXML::Element->new('value'); |
261
|
|
|
|
|
|
|
$value_elem->appendTextNode(encode('utf-8', $value)) if defined($value); |
262
|
|
|
|
|
|
|
$elem->appendChild($value_elem); |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
return $elem; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=head2 to_hash( ... ) |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
Export the Augeas tree to a hash. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=over |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=item path |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
C is the Augeas path to export. If ommitted, it will default to '/files'. |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=item exclude |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
A list of label patterns to exclude from the export. |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=back |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=cut |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub to_hash { |
289
|
|
|
|
|
|
|
my $self = shift; |
290
|
|
|
|
|
|
|
my %args = @_; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
my $path = $args{path}; |
293
|
|
|
|
|
|
|
my @excludes = ($args{exclude}); |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# Default path |
296
|
|
|
|
|
|
|
$path ||= $PATH; |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
my @file_elems = (node_to_hash($self, $path, \@excludes)); |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
my %hash = ( |
301
|
|
|
|
|
|
|
files => \@file_elems, |
302
|
|
|
|
|
|
|
); |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
return \%hash; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub node_to_hash { |
309
|
|
|
|
|
|
|
my ($self, $path, $excludes) = @_; |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
# Get label from path |
312
|
|
|
|
|
|
|
my $label = get_label($path); |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Filter excludes |
315
|
|
|
|
|
|
|
return if exclude_match($label, $excludes); |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# Sanitize path for augeas requests |
318
|
|
|
|
|
|
|
$path = sanitize_path($path); |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
my $aug = $self->{aug}; |
321
|
|
|
|
|
|
|
my @children = $aug->match("$path/*"); |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# Parse children |
324
|
|
|
|
|
|
|
my @child_elems; |
325
|
|
|
|
|
|
|
for my $child (@children) { |
326
|
|
|
|
|
|
|
my @new_child_elems = (node_to_hash($self, $child, $excludes)); |
327
|
|
|
|
|
|
|
map { push @child_elems, $_ } @new_child_elems; |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# Directories don't get their own node |
331
|
|
|
|
|
|
|
return @child_elems if ($self->is_dir($path)); |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# Initialize array |
334
|
|
|
|
|
|
|
my %hash; |
335
|
|
|
|
|
|
|
my $node_hash; |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
if (is_file($self, $path)) { |
338
|
|
|
|
|
|
|
my $file_path = get_file_path($path); |
339
|
|
|
|
|
|
|
$node_hash = \%{$hash{$file_path}}; |
340
|
|
|
|
|
|
|
} else { |
341
|
|
|
|
|
|
|
$node_hash = \%{$hash{$label}}; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# Append children |
345
|
|
|
|
|
|
|
map { push @{$node_hash->{children}}, $_ } @child_elems; |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# Add value to element |
348
|
|
|
|
|
|
|
my $value = $aug->get($path); |
349
|
|
|
|
|
|
|
$node_hash->{value} = $value if defined($value); |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
return \%hash; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=head2 to_yaml( ... ) |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
Export the Augeas tree to YAML. |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
=over |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=item path |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
C is the Augeas path to export. If ommitted, it will default to '/files'. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=item exclude |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
A list of label patterns to exclude from the export. |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=back |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=cut |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub to_yaml { |
374
|
|
|
|
|
|
|
my $self = shift; |
375
|
|
|
|
|
|
|
my %args = @_; |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
my $hash = $self->to_hash(%args); |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
return YAML::Dump($hash); |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head2 to_json( ... ) |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
Export the Augeas tree to JSON. |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=over |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=item path |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
C is the Augeas path to export. If ommitted, it will default to '/files'. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=item exclude |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
A list of label patterns to exclude from the export. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=back |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=cut |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
sub to_json { |
402
|
|
|
|
|
|
|
my $self = shift; |
403
|
|
|
|
|
|
|
my %args = @_; |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
my $hash = $self->to_hash(%args); |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
my $json = new JSON; |
408
|
|
|
|
|
|
|
return $json->encode($hash); |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=head2 from_xml( ... ) |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
Restore the Augeas tree from an XML::LibXML::Document object. |
415
|
|
|
|
|
|
|
This method considers the files listed in the XML document, |
416
|
|
|
|
|
|
|
and replaces the corresponding files in the Augeas tree with |
417
|
|
|
|
|
|
|
the contents of the XML. |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=over |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
=item xml |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
The XML::LibXML::Document to use as source for import. |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
=item create_dirs |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
Boolean value, whether to create the directories if missing. |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=back |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=cut |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub from_xml { |
435
|
|
|
|
|
|
|
my $self = shift; |
436
|
|
|
|
|
|
|
my %args = @_; |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
die "E: No XML provided." unless(defined($args{xml})); |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
my $xml = $args{xml}; |
441
|
|
|
|
|
|
|
my $create_dirs = $args{create_dirs}; |
442
|
|
|
|
|
|
|
my $aug = $self->{aug}; |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
my @files = $xml->find('/augeas/files/file')->get_nodelist(); |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
if ($#files < 0) { |
447
|
|
|
|
|
|
|
warn "W: The XML document contains no file to restore."; |
448
|
|
|
|
|
|
|
return; |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
# Get augeas root to create directories |
452
|
|
|
|
|
|
|
my $aug_root = $self->{aug_root}; |
453
|
|
|
|
|
|
|
die "E: Could not determine Augeas root needed to create directories." |
454
|
|
|
|
|
|
|
if ($create_dirs && !defined($aug_root)); |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# Add each file to the Augeas tree |
457
|
|
|
|
|
|
|
for my $file (@files) { |
458
|
|
|
|
|
|
|
my $path = $file->getAttribute('path'); |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# Create directories if requested |
461
|
|
|
|
|
|
|
if($create_dirs) { |
462
|
|
|
|
|
|
|
if ($path =~ m|^(.*)/([^/]+)$|) { |
463
|
|
|
|
|
|
|
my $dir = "${aug_root}${1}"; |
464
|
|
|
|
|
|
|
unless (-d $dir) { |
465
|
|
|
|
|
|
|
mkpath($dir) or die "E: Failed to create directory $dir: $!"; |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
} else { |
468
|
|
|
|
|
|
|
die "E: Could not get directory from file path $path."; |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
my $aug_path = "/files${path}"; |
473
|
|
|
|
|
|
|
# Clean the Augeas tree for this file |
474
|
|
|
|
|
|
|
$aug->rm($aug_path); |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
for my $node ($file->childNodes) { |
477
|
|
|
|
|
|
|
$self->xml_to_node($node, $aug_path); |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
$aug->save; |
482
|
|
|
|
|
|
|
$aug->print('/augeas//error'); |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
sub xml_to_node { |
487
|
|
|
|
|
|
|
my ($self, $elem, $path) = @_; |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
my $aug = $self->{aug}; |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
my $name = $elem->nodeName; |
492
|
|
|
|
|
|
|
my $label = $elem->getAttribute('label'); |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# Ignore stat nodes |
495
|
|
|
|
|
|
|
return if ($name eq 'stat'); |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
my $matchpath = "$path/*[last()]"; |
498
|
|
|
|
|
|
|
$matchpath = sanitize_path($matchpath); |
499
|
|
|
|
|
|
|
my $lastpath = sanitize_path($aug->match("$path/*[last()]")); |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
if(defined($lastpath)) { |
502
|
|
|
|
|
|
|
# Insert last node |
503
|
|
|
|
|
|
|
$aug->insert($label, "after", $lastpath); |
504
|
|
|
|
|
|
|
} else { |
505
|
|
|
|
|
|
|
# Config::Augeas doesn't take undef |
506
|
|
|
|
|
|
|
# as a correct value or provide clear |
507
|
|
|
|
|
|
|
# This is an ugly trick to do the same |
508
|
|
|
|
|
|
|
# hoping the previous children did not |
509
|
|
|
|
|
|
|
# create a ##foo node |
510
|
|
|
|
|
|
|
my $create_path = sanitize_path("$path/$label/#foo"); |
511
|
|
|
|
|
|
|
$aug->set($create_path, "foo"); |
512
|
|
|
|
|
|
|
$aug->rm($create_path); |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
$matchpath = sanitize_path("${path}/${label}[last()]"); |
516
|
|
|
|
|
|
|
my $newpath = sanitize_path($aug->match($matchpath)); |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
my $value; |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
for my $child ($elem->childNodes()) { |
521
|
|
|
|
|
|
|
if ($child->nodeName eq 'value') { |
522
|
|
|
|
|
|
|
# Text node |
523
|
|
|
|
|
|
|
$value = $child->textContent; |
524
|
|
|
|
|
|
|
} else { |
525
|
|
|
|
|
|
|
$self->xml_to_node($child, $newpath); |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
if (defined($value)) { |
530
|
|
|
|
|
|
|
$aug->set($newpath, $value); |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
############## |
536
|
|
|
|
|
|
|
# Useful subs |
537
|
|
|
|
|
|
|
############## |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
sub get_file_path { |
540
|
|
|
|
|
|
|
my ($path) = @_; |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
my $file_path = $path; |
543
|
|
|
|
|
|
|
$file_path =~ s|^/files||; |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
return $file_path; |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
sub get_label { |
550
|
|
|
|
|
|
|
my ($path) = @_; |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
# Get label from path |
553
|
|
|
|
|
|
|
my $label = ''; |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
if ($path =~ m|.*/([^/\[]+)(\[\d+\])?|) { |
556
|
|
|
|
|
|
|
$label = $1; |
557
|
|
|
|
|
|
|
} else { |
558
|
|
|
|
|
|
|
die "E: Could not parse $path\n"; |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
return $label; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub exclude_match { |
566
|
|
|
|
|
|
|
my ($label, $excludes) = @_; |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
# Filter excludes |
569
|
|
|
|
|
|
|
for my $exclude (@$excludes) { |
570
|
|
|
|
|
|
|
if ($exclude && $label =~ /$exclude/) { |
571
|
|
|
|
|
|
|
return 1; |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
return 0; |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
sub sanitize_path { |
580
|
|
|
|
|
|
|
my ($path) = @_; |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
return unless ($path); |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
# Sanitize path for augeas requests |
585
|
|
|
|
|
|
|
$path =~ s|(?<=[^\\]) |\\ |g; |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
return $path; |
588
|
|
|
|
|
|
|
} |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
sub is_file { |
592
|
|
|
|
|
|
|
my ($self, $path) = @_; |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
my $aug_path = "/augeas${path}/path"; |
595
|
|
|
|
|
|
|
my $aug = $self->{aug}; |
596
|
|
|
|
|
|
|
my $count = $aug->count_match($aug_path); |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
# Not a file if there is no path subnode |
599
|
|
|
|
|
|
|
return 0 if ($count == 0); |
600
|
|
|
|
|
|
|
# Check that the subnode has the right value |
601
|
|
|
|
|
|
|
return 1 if ($aug->get($aug_path) eq $path); |
602
|
|
|
|
|
|
|
# Otherwise it's not a file |
603
|
|
|
|
|
|
|
return 0; |
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
sub is_dir { |
608
|
|
|
|
|
|
|
my ($self, $path) = @_; |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
my $aug_path = "/augeas${path}"; |
611
|
|
|
|
|
|
|
my $aug = $self->{aug}; |
612
|
|
|
|
|
|
|
my $count = $aug->count_match($aug_path); |
613
|
|
|
|
|
|
|
my $value = $aug->get($aug_path); |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# A directory is not a file |
616
|
|
|
|
|
|
|
# but its path must exist in /augeas |
617
|
|
|
|
|
|
|
# and have no value associated to it |
618
|
|
|
|
|
|
|
return 1 if (!$self->is_file($path) && $count == 1 |
619
|
|
|
|
|
|
|
&& !defined($value)); |
620
|
|
|
|
|
|
|
# Otherwise it's not a directory |
621
|
|
|
|
|
|
|
return 0; |
622
|
|
|
|
|
|
|
} |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
sub stat_to_xml { |
626
|
|
|
|
|
|
|
my ($self, $path) = @_; |
627
|
|
|
|
|
|
|
my $aug_root = $self->{aug_root}; |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
my %stat; |
630
|
|
|
|
|
|
|
($stat{dev}, $stat{ino}, $stat{mode}, $stat{nlink}, |
631
|
|
|
|
|
|
|
$stat{uid}, $stat{gid}, $stat{rdev}, $stat{size}, |
632
|
|
|
|
|
|
|
$stat{atime}, $stat{mtime}, $stat{ctime}, |
633
|
|
|
|
|
|
|
$stat{blksize}, $stat{blocks}) = stat("${aug_root}${path}"); |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
my $stat_elem = XML::LibXML::Element->new('stat'); |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
for my $k (keys(%stat)) { |
638
|
|
|
|
|
|
|
$stat_elem->setAttribute($k, $stat{$k}); |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
return $stat_elem; |
642
|
|
|
|
|
|
|
} |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
__END__ |