Commit | Line | Data |
60e1cc39 |
1 | package System::Introspector::State; |
2 | use Moo; |
b079a95d |
3 | use File::Tree::Snapshot; |
60e1cc39 |
4 | use System::Introspector::Gatherer; |
5 | |
0a11cf83 |
6 | use JSON::Diffable qw( encode_json ); |
7 | |
60e1cc39 |
8 | has config => (is => 'ro', required => 1); |
9 | |
b079a95d |
10 | has root => (is => 'ro', required => 1); |
60e1cc39 |
11 | |
b079a95d |
12 | sub user { $_[0]->config->user } |
60e1cc39 |
13 | |
b079a95d |
14 | sub sudo_user { $_[0]->config->sudo_user } |
a5e1e1c6 |
15 | |
b079a95d |
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 | } |
a5e1e1c6 |
23 | |
b079a95d |
24 | sub introspectors { |
25 | my ($self, $group) = @_; |
26 | return $self->config->config_for_group($group)->{introspect}; |
27 | } |
60e1cc39 |
28 | |
b079a95d |
29 | sub fetch_and_store { |
30 | my ($self, $host, @groups) = @_; |
31 | my $data = $self->fetch($host, @groups); |
32 | return $self->_store($host, $data); |
33 | } |
60e1cc39 |
34 | |
35 | sub fetch { |
b079a95d |
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); |
a5e1e1c6 |
45 | my (@sudo, @nosudo); |
46 | push(@{ $spec->{$_}{sudo} ? \@sudo : \@nosudo}, [$_, $spec->{$_}]) |
47 | for sort keys %$spec; |
48 | my %report; |
49 | if (@nosudo) { |
b079a95d |
50 | my $gatherer = $self->_create_gatherer(host => $host); |
a5e1e1c6 |
51 | %report = %{ $self->_fetch_with_gatherer($gatherer, @nosudo) || {} }; |
52 | } |
53 | if (@sudo) { |
b079a95d |
54 | my $gatherer = $self->_create_gatherer(sudo => 1, host => $host); |
a5e1e1c6 |
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) = @_; |
60e1cc39 |
62 | my %report; |
a5e1e1c6 |
63 | for my $class_spec (@spec) { |
64 | my ($class_base, $args) = @$class_spec; |
65 | print "Gathering $class_base data\n"; |
60e1cc39 |
66 | $report{ $class_base } = $gatherer |
a5e1e1c6 |
67 | ->gather($class_base, $args); |
60e1cc39 |
68 | } |
a5e1e1c6 |
69 | print "All gathered\n"; |
60e1cc39 |
70 | return \%report; |
71 | } |
72 | |
b079a95d |
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; |
60e1cc39 |
82 | } |
83 | |
84 | sub _store { |
b079a95d |
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; |
60e1cc39 |
103 | } |
60e1cc39 |
104 | return 1; |
105 | } |
106 | |
107 | sub _cleanup { |
b079a95d |
108 | my ($self, $storage, $known_files) = @_; |
60e1cc39 |
109 | my %known = map { ($_ => 1) } @$known_files; |
b079a95d |
110 | my @files = $storage->find_files('json'); |
60e1cc39 |
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 { |
a5e1e1c6 |
121 | my ($self, %arg) = @_; |
122 | return System::Introspector::Gatherer->new_from_spec( |
123 | user => $self->user, |
b079a95d |
124 | host => $arg{host}, |
a5e1e1c6 |
125 | sudo_user => $arg{sudo} && $self->sudo_user, |
126 | ); |
60e1cc39 |
127 | } |
128 | |
129 | 1; |
cd5c3d43 |
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 |