1#
2# Licensed to the Apache Software Foundation (ASF) under one
3# or more contributor license agreements.  See the NOTICE file
4# distributed with this work for additional information
5# regarding copyright ownership.  The ASF licenses this file
6# to you under the Apache License, Version 2.0 (the
7# "License"); you may not use this file except in compliance
8# with the License.  You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18############################################################################
19# This is a simple module to let you read, modify and add to an Apache
20# Traffic Server records.config file. The idea is that you would write a
21# simple script (like example below) to update a "stock" records.config with
22# the changes applicable to your application. This allows you to uprade to
23# a newer default config file from a new release. See the embedded
24# perldoc for more details.
25############################################################################
26
27package Apache::TS::Config::Records;
28
29use Apache::TS::Config;
30
31use warnings;
32use strict;
33require 5.006;
34
35use Carp;
36
37our $VERSION = "1.0";
38
39#
40# Constructor
41#
42sub new
43{
44    my ($class, %args) = @_;
45    my $self = {};
46    my $fn   = $args{file};
47
48    $fn = $args{filename} unless defined($fn);
49    $fn = "-"             unless defined($fn);
50
51    $self->{_filename} = $fn;    # Filename to open when loading and saving
52    $self->{_configs}  = [];     # Storage, and to to preserve order
53    $self->{_lookup}   = {};     # For faster lookup, indexes into the above
54    $self->{_ix}       = -1;     # Empty
55    bless $self, $class;
56
57    $self->load() if $self->{_filename};
58
59    return $self;
60}
61
62#
63# Load a records.config file
64#
65sub load
66{
67    my $self = shift;
68    my %args = @_;
69    my $fn   = $args{file};
70
71    $fn = $args{filename}    unless defined($fn);
72    $fn = $self->{_filename} unless defined($fn);
73
74    open(FH, "<$fn") || die "Can't open file $fn for reading";
75    while (<FH>) {
76        chomp;
77        my @p = split(/\s+/, $_, 4);
78
79        push(@{$self->{_configs}}, [$_, \@p, TS_CONF_UNMODIFIED]);
80
81        ++($self->{_ix});
82        next unless ($#p == 3) && (($p[0] eq "LOCAL") || ($p[0] eq "CONFIG"));
83        print "Warning! duplicate configuration $p[1]\n" if exists($self->{_lookup}->{$p[1]});
84
85        $self->{_lookup}->{$p[1]} = $self->{_ix};
86    }
87}
88
89#
90# Get an existing configuration line, as an anon array.
91#
92sub get
93{
94    my $self = shift;
95    my %args = @_;
96    my $c    = $args{conf};
97
98    $c = $args{config} unless defined($c);
99    my $ix = $self->{_lookup}->{$c};
100
101    return [] unless defined($ix);
102    return $self->{_configs}->[$ix];
103}
104
105#
106# Modify one configuration value
107#
108sub set
109{
110    my $self = shift;
111    my %args = @_;
112    my $c    = $args{conf};
113    my $v    = $args{val};
114
115    $c = $args{config} unless defined($c);
116    $v = $args{value}  unless defined($v);
117
118    my $ix = $self->{_lookup}->{$c};
119
120    if (!defined($ix)) {
121        my $type = $args{type};
122
123        $type = "INT" unless defined($type);
124        $self->append(line => "CONFIG $c $type $v");
125    } else {
126        my $val = $self->{_configs}->[$ix];
127
128        @{$val->[1]}[3] = $v;
129        $val->[2] = TS_CONF_MODIFIED;
130    }
131}
132
133#
134# Remove a configuration from the file.
135#
136sub remove
137{
138    my $self = shift;
139    my %args = @_;
140    my $c    = $args{conf};
141
142    $c = $args{config} unless defined($c);
143
144    my $ix = $self->{_lookup}->{$c};
145
146    $self->{_configs}->[$ix]->[2] = TS_CONF_REMOVED if defined($ix);
147}
148
149#
150# Append anything to the "end" of the configuration.
151#
152sub append
153{
154    my $self = shift;
155    my %args = @_;
156    my $line = $args{line};
157
158    my @p = split(/\s+/, $line, 4);
159
160    # Don't appending duplicated configs
161    if (($#p == 3) && exists($self->{_lookup}->{$p[1]})) {
162        print "Warning: duplicate configuration $p[1]\n";
163        return;
164    }
165
166    push(@{$self->{_configs}}, [$line, \@p, TS_CONF_UNMODIFIED]);
167    ++($self->{_ix});
168    $self->{_lookup}->{$p[1]} = $self->{_ix} if ($#p == 3) && (($p[0] eq "LOCAL") || ($p[0] eq "CONFIG"));
169}
170
171#
172# Write the new configuration file to STDOUT, or provided
173#
174sub write
175{
176    my $self = shift;
177    my %args = @_;
178    my $fn   = $args{file};
179
180    $fn = $args{filename} unless defined($fn);
181    $fn = "-"             unless defined($fn);
182
183    if ($fn ne "-") {
184        close(STDOUT);
185        open(STDOUT, ">$fn") || die "Can't open $fn for writing";
186    }
187
188    foreach (@{$self->{_configs}}) {
189        if ($_->[2] == TS_CONF_UNMODIFIED) {
190            print $_->[0], "\n";
191        } elsif ($_->[2] == TS_CONF_MODIFIED) {
192            print join(" ", @{$_->[1]}), "\n";
193        } else {
194            # No-op if removed
195        }
196    }
197}
1981;
199
200__END__
201
202=head1 NAME
203
204Apache::TS::Config::Records - Manage the Apache Traffic Server records.config file
205
206=head1 SYNOPSIS
207
208  #!/usr/bin/perl
209
210  use Apache::TS::Config::Records;
211
212  my $r = new Apache::TS::Config::Records(file => "/tmp/records.config");
213  $r->set(conf => "proxy.config.log.extended_log_enabled",
214          val => "123");
215  $r->write(file => "/tmp/records.config.new");
216
217=head1 DESCRIPTION
218
219This module implements a convenient interface to read, modify and save
220the records.config file as used by Apache Traffic Server.
221
222Instantiating a new Config::Records class, with a file provided, will
223automatically load that configuration. Don't call the load() method
224explicitly in this case.
225
226=head2 API Methods
227
228The following are methods in the Records class.
229
230=over 8
231
232=item new
233
234Instantiate a new object. The file name is optionally provided, and if
235present that file is immediately loaded (see the load() method
236below). Example:
237
238  my $r = new Apache::TS::Config::Records(file => $fname);
239
240=item load
241
242Explicitly load a configuration file, merging the items with any
243existing values. This is useful to for example merge multiple
244configuration into one single structure
245
246=item get
247
248Get an existing configuration line. This is useful for
249detecting that a config exists or not, for example. The
250return value is an anonymous array like
251
252  [<line string>, [value split into 4 fields, flag if changed]
253
254
255You probably shouldn't modify this array.
256
257=item set
258
259Modify one configuration value, with the provided value. Both the conf
260name and the value are required. Example:
261
262  $r->set(conf => "proxy.config.exec_thread.autoconfig",
263          val => "0");
264
265conf is short for "config", val is short for "value", and all are
266acceptable.
267
268=item remove
269
270Remove a specified configuration, the mandatory option is conf (or
271"config"). Example:
272
273  $r->remove(conf => "proxy.config.exec_thread.autoconfig");
274
275=item append
276
277Append a string to the "end" of the finished configuration file. We
278will assure that no duplicated configurations are added. The input is a
279single line, as per the normal records.config syntax. The purpose of
280this is to add new sections to the configuration, with appropriate
281comments etc. Example:
282
283  $r->append(line => "");
284  $r->append(line => "# My local stuff");
285  $r->set(conf => "proxy.config.dns.dedicated_thread",
286          val => "1");
287
288=item write
289
290Write the new configuration file to STDOUT, or a filename if
291provided. Example:
292
293  $r->write(file => "/etc/trafficserver/records.config");
294
295=back
296
297=head1 SEE ALSO
298
299L<Apache::TS::Config>
300
301=cut
302