1package Munin::Node::Configure::PluginList;
2
3use strict;
4use warnings;
5
6use File::Basename qw(fileparse);
7
8use Munin::Node::Service;
9use Munin::Node::Configure::Plugin;
10use Munin::Node::Configure::History;
11use Munin::Node::Configure::Debug;
12
13
14sub new
15{
16    my ($class, %opts) = @_;
17
18    my $libdir     = delete $opts{libdir}     or die "Must specify the directory\n";
19    my $servicedir = delete $opts{servicedir} or die "Must specify the service directory\n";
20
21    my $library  = Munin::Node::Service->new(servicedir => $libdir);
22    my $services = Munin::Node::Service->new(servicedir => $servicedir);
23
24    my $families = delete $opts{families} or die "Must provide a list of families to load\n";
25    my $newer    = delete $opts{newer};
26
27    my %plugin = (
28        libdir     => $libdir,
29        library    => $library,
30
31        servicedir => $servicedir,
32        services   => $services,
33
34        families   => $families,
35        newer      => $newer,
36
37        %opts,
38    );
39
40    return bless \%plugin, $class;
41}
42
43
44### Plugin and service enumeration #############################################
45
46sub load
47{
48    my ($self) = @_;
49    $self->_load_available();
50    $self->_load_installed();
51    return;
52}
53
54
55sub _load_available
56{
57    my ($self) = @_;
58
59    my @families = @{$self->{families}};
60    my %found;
61    my $plugin_count = 0;
62
63    my $history = Munin::Node::Configure::History->new(
64        history_file => "$self->{libdir}/plugins.history",
65        newer        => $self->{newer},
66    );
67    $history->load;
68
69    DEBUG("Searching '$self->{libdir}' for available plugins.");
70
71    foreach my $item (_valid_files($self->{library})) {
72        my $path = $item->{path};
73        my $plug = $item->{name};
74
75        DEBUG("Considering '$path'");
76
77        my $plugin = Munin::Node::Configure::Plugin->new(name => $plug, path => $path);
78
79        $plugin->read_magic_markers();
80
81        unless ($plugin->in_family(@families)) {
82            DEBUG("\tFamily '$plugin->{family}' is currently ignored.  Skipping.");
83            next;
84        }
85
86        if ($history->too_old($plugin)) {
87            DEBUG("\tPlugin is older than $self->{newer}.  Skipping.");
88            next;
89        }
90
91        $found{$plug} = $plugin;
92        $plugin_count++;
93    }
94
95    $self->{plugins} = \%found;
96    DEBUG("$plugin_count plugins available.");
97
98    return;
99}
100
101
102sub _load_installed
103{
104    my ($self) = @_;
105    my $service_count = 0;  # the number of services currently installed.
106
107    DEBUG("Searching '$self->{servicedir}' for installed services.");
108
109    foreach my $item (_valid_files($self->{services})) {
110        my $path    = $item->{path};
111        my $service = $item->{name};
112
113        my $realfile;
114        # Ignore non-symlinks, and symlinks that point anywhere other
115        # than the plugin library
116        next unless -l $path;
117        unless ($realfile = readlink($path)) {
118            # FIXME: should be a given, since it's tested by is_a_runnable_service()
119            DEBUG("Warning: symlink '$path' is broken.");
120            next;
121        }
122        next unless ($realfile =~ /^$self->{libdir}\//);
123
124        DEBUG("Found '$service'");
125
126        $realfile = fileparse($realfile);
127        unless ($self->{plugins}{$realfile}) {
128            DEBUG("\tCorresponds to an ignored plugin ($realfile).  Skipping.");
129            next;
130        }
131
132        $self->{plugins}{$realfile}->add_instance($service);
133        $service_count++;
134    }
135
136    DEBUG("$service_count services currently installed.");
137    return;
138}
139
140
141sub list
142{
143    my ($self) = @_;
144    my @plugins;
145    foreach my $plug (sort keys %{$self->{plugins}}) {
146        push @plugins, $self->{plugins}{$plug};
147    }
148    return @plugins;
149}
150
151
152sub names { return keys %{(shift)->{plugins}} }
153
154
155sub _valid_files
156{
157    my ($dir) = @_;
158    return map { { path => "$dir->{servicedir}/$_", name => $_ } } $dir->list;
159}
160
161
1621;
163
164__END__
165
166=head1 NAME
167
168Munin::Node::Configure::PluginList - Loading and listing a collection of plugins
169
170
171=head1 SYNOPSIS
172
173  my $plugins = Munin::Node::Configure::PluginList->new(
174        libdir     => '/usr/share/munin/plugins/',
175        servicedir => '/etc/munin/plugins/',
176  );
177  $plugins->load('auto');
178  foreach my $plugin ($plugins->list) {
179        # do something to each 'auto' plugin in turn
180  }
181
182
183=head1 SUBROUTINES
184
185=over
186
187=item B<new(%args)>
188
189Constructor.
190
191Required arguments are 'libdir' and 'servicedir', which are the plugin library
192and service directory, respectively.
193
194
195=item B<load(@families)>
196
197Finds all the plugins in 'libdir' that are in any of @families, and any
198instances of these plugins in 'servicedir'.
199
200
201=item B<list()>
202
203Returns a list of Munin::Node::Configure::Plugin objects currently loaded,
204sorted alphabetically by name.
205
206
207=item B<names()>
208
209Returns the names of the currently-loaded plugins.
210
211=back
212
213=cut
214# vim: sw=4 : ts=4 : expandtab
215