3f208763388360ef7de0432ce9b92eade0fc4fd0
[scpubgit/System-Introspector.git] / lib / System / Introspector / State.pm
1 package System::Introspector::State;
2 use Moo;
3 use File::Tree::Snapshot;
4 use System::Introspector::Gatherer;
5
6 use JSON::Diffable qw( encode_json );
7
8 has config => (is => 'ro', required => 1);
9
10 has root => (is => 'ro', required => 1);
11
12 sub user { $_[0]->config->user }
13
14 sub sudo_user { $_[0]->config->sudo_user }
15
16 sub gather {
17     my ($self, @groups) = @_;
18     for my $host ($self->config->hosts) {
19         $self->fetch_and_store($host, @groups);
20     }
21     return 1;
22 }
23
24 sub introspectors {
25     my ($self, $group) = @_;
26     return $self->config->config_for_group($group)->{introspect};
27 }
28
29 sub fetch_and_store {
30     my ($self, $host, @groups) = @_;
31     my $data = $self->fetch($host, @groups);
32     return $self->_store($host, $data);
33 }
34
35 sub fetch {
36     my ($self, $host, @groups) = @_;
37     return +{ map {
38         ($_, $self->fetch_group($host, $_));
39     } @groups };
40 }
41
42 sub fetch_group {
43     my ($self, $host, $group) = @_;
44     my $spec = $self->introspectors($group);
45     my (@sudo, @nosudo);
46     push(@{ $spec->{$_}{sudo} ? \@sudo : \@nosudo}, [$_, $spec->{$_}])
47         for sort keys %$spec;
48     my %report;
49     if (@nosudo) {
50         my $gatherer = $self->_create_gatherer(host => $host);
51         %report = %{ $self->_fetch_with_gatherer($gatherer, @nosudo) || {} };
52     }
53     if (@sudo) {
54         my $gatherer = $self->_create_gatherer(sudo => 1, host => $host);
55         %report = (%report, %{ $self->_fetch_with_gatherer($gatherer, @sudo) || {} });
56     }
57     return \%report;
58 }
59
60 sub _fetch_with_gatherer {
61     my ($self, $gatherer, @spec) = @_;
62     my %report;
63     for my $class_spec (@spec) {
64         my ($class_base, $args) = @$class_spec;
65         print "Gathering $class_base data\n";
66         $report{ $class_base } = $gatherer
67             ->gather($class_base, $args);
68     }
69     print "All gathered\n";
70     return \%report;
71 }
72
73 sub storage {
74     my ($self, @path) = @_;
75     my $storage = File::Tree::Snapshot->new(
76         allow_empty  => 0,
77         storage_path => join('/', $self->root, @path),
78     );
79     $storage->create
80         unless $storage->exists;
81     return $storage;
82 }
83
84 sub _store {
85     my ($self, $host, $data) = @_;
86     for my $group (sort keys %$data) {
87         my $storage = $self->storage($host, $group);
88         my $gathered = $data->{$group};
89         my @files;
90         for my $class (sort keys %$gathered) {
91             my $file = sprintf '%s.json', join '/',
92                 map lc, map {
93                     s{([a-z0-9])([A-Z])}{${1}_${2}}g;
94                     $_;
95                 } split m{::}, $class;
96             my $fh = $storage->open('>:utf8', $file, mkpath => 1);
97             print "Writing $file\n";
98             print $fh encode_json($gathered->{$class});
99             push @files, $storage->file($file);
100         }
101         $self->_cleanup($storage, [@files]);
102         $storage->commit;
103     }
104     return 1;
105 }
106
107 sub _cleanup {
108     my ($self, $storage, $known_files) = @_;
109     my %known = map { ($_ => 1) } @$known_files;
110     my @files = $storage->find_files('json');
111     for my $file (@files) {
112         next if $known{$file};
113         print "Removing $file\n";
114         unlink($file)
115             or die "Unable to remove '$file': $!\n";
116     }
117     return 1;
118 }
119
120 sub _create_gatherer {
121     my ($self, %arg) = @_;
122     return System::Introspector::Gatherer->new_from_spec(
123         user        => $self->user,
124         host        => $arg{host},
125         sudo_user   => $arg{sudo} && $self->sudo_user,
126     );
127 }
128
129 1;
130
131 =head1 NAME
132
133 System::Introspector::State - Gather system state
134
135 =head1 SYNOPSIS
136
137     my $state = System::Introspector::State->new(
138         host    => 'foo.example.com',
139         storage => $storage_obj,
140         config  => {
141             introspect => [qw( ProbeName )],
142         },
143     );
144
145     my $data = $state->fetch;
146     $state->fetch_and_store;
147
148 =head1 DESCRIPTION
149
150 Gathers system introspection data based on configuration and stores
151 it with a L<File::Tree::Snapshot> object.
152
153 =head1 ATTRIBUTES
154
155 =head2 config
156
157 A hash reference containing a C<introspect> key with an array reference
158 value containing a list of probe names without the
159 C<System::Introspector::Probe::> prefix. This attribute is required.
160
161 =head2 host
162
163 An optional hostname. If no hostname is supplied, the local configuration
164 data will be fetched.
165
166 =head2 storage
167
168 A L<File::Tree::Snapshot> object.
169
170 =head1 METHODS
171
172 =head2 fetch
173
174     my $data = $state->fetch;
175
176 Fetches all probe data.
177
178 =head2 fetch_and_store
179
180     $state->fetch_and_store;
181
182 Fetches all probe data and stores it in the L</storage>.
183
184 =cut