GetOptions(
'c|config=s' => \my $config_file,
's|storage=s' => \my $storage_dir,
- 'H|host=s' => \my $hostname,
- 'U|user=s' => \my $username,
- 'allow-empty' => \my $allow_empty,
'a|all' => \my $update_all,
'g|group=s' => \my @update_groups,
'h|help' => sub { pod2usage(0) },
die "Requires --all or --group option\n"
unless $update_all or @update_groups;
-die "The --user option also requires a --host to be set\n"
- if defined($username) and not defined($hostname);
-
my $config = System::Introspector::Config->new(
config_file => (defined($config_file)
? $config_file
@update_groups = $config->groups
if $update_all;
-
-my $sudo_user = $config->sudo_user;
-
-for my $group (@update_groups) {
- my $group_dir = "$storage_dir/$group";
- print "Group $group at $group_dir\n";
- my $storage = File::Tree::Snapshot->new(
- storage_path => $group_dir,
- allow_empty => $allow_empty,
- );
- $storage->create
- unless $storage->exists;
- my $state = System::Introspector::State->new(
- defined($hostname) ? (host => $hostname) : (),
- defined($username) ? (user => $username) : (),
- defined($sudo_user) ? (sudo_user => $sudo_user) : (),
- storage => $storage,
- config => $config->config_for_group($group),
- );
- eval { $state->fetch_and_store };
- if (my $error = $@) {
- warn "Error: $error\n";
- $storage->reset;
- }
- else {
- $storage->commit;
- }
-}
+
+my $state = System::Introspector::State->new(
+ config => $config,
+ root => $storage_dir,
+);
+
+$state->gather(@update_groups);
__END__
package System::Introspector::State;
use Moo;
+use File::Tree::Snapshot;
use System::Introspector::Gatherer;
use JSON::Diffable qw( encode_json );
has config => (is => 'ro', required => 1);
-has introspectors => (is => 'lazy');
+has root => (is => 'ro', required => 1);
-has host => (is => 'ro');
+sub user { $_[0]->config->user }
-has user => (is => 'ro');
+sub sudo_user { $_[0]->config->sudo_user }
-has sudo_user => (is => 'ro');
+sub gather {
+ my ($self, @groups) = @_;
+ for my $host ($self->config->hosts) {
+ $self->fetch_and_store($host, @groups);
+ }
+ return 1;
+}
-has storage => (is => 'ro', required => 1);
+sub introspectors {
+ my ($self, $group) = @_;
+ return $self->config->config_for_group($group)->{introspect};
+}
-has node_path => (is => 'lazy');
+sub fetch_and_store {
+ my ($self, $host, @groups) = @_;
+ my $data = $self->fetch($host, @groups);
+ return $self->_store($host, $data);
+}
sub fetch {
- my ($self) = @_;
- my $spec = $self->introspectors;
+ my ($self, $host, @groups) = @_;
+ return +{ map {
+ ($_, $self->fetch_group($host, $_));
+ } @groups };
+}
+
+sub fetch_group {
+ my ($self, $host, $group) = @_;
+ my $spec = $self->introspectors($group);
my (@sudo, @nosudo);
push(@{ $spec->{$_}{sudo} ? \@sudo : \@nosudo}, [$_, $spec->{$_}])
for sort keys %$spec;
my %report;
if (@nosudo) {
- my $gatherer = $self->_create_gatherer;
+ my $gatherer = $self->_create_gatherer(host => $host);
%report = %{ $self->_fetch_with_gatherer($gatherer, @nosudo) || {} };
}
if (@sudo) {
- my $gatherer = $self->_create_gatherer(sudo => 1);
+ my $gatherer = $self->_create_gatherer(sudo => 1, host => $host);
%report = (%report, %{ $self->_fetch_with_gatherer($gatherer, @sudo) || {} });
}
return \%report;
return \%report;
}
-sub fetch_and_store {
- my ($self) = @_;
- my $data = $self->fetch;
- return $self->_store($data);
-}
-
-sub _build_node_path {
- my ($self) = @_;
- return defined($self->host)
- ? sprintf('host/%s', $self->host)
- : 'local';
-}
-
-sub _build_introspectors {
- my ($self) = @_;
- return $self->config->{introspect};
+sub storage {
+ my ($self, @path) = @_;
+ my $storage = File::Tree::Snapshot->new(
+ allow_empty => 0,
+ storage_path => join('/', $self->root, @path),
+ );
+ $storage->create
+ unless $storage->exists;
+ return $storage;
}
sub _store {
- my ($self, $data) = @_;
- my $storage = $self->storage;
- my @files;
- for my $class (sort keys %$data) {
- my $file = sprintf '%s.json', join '/',
- node => $self->node_path,
- map lc, map {
- s{([a-z0-9])([A-Z])}{${1}_${2}}g;
- $_;
- } split m{::}, $class;
- my $fh = $storage->open('>:utf8', $file, mkpath => 1);
- print "Writing $file\n";
- print $fh encode_json($data->{$class});
- push @files, $storage->file($file);
+ my ($self, $host, $data) = @_;
+ for my $group (sort keys %$data) {
+ my $storage = $self->storage($host, $group);
+ my $gathered = $data->{$group};
+ my @files;
+ for my $class (sort keys %$gathered) {
+ my $file = sprintf '%s.json', join '/',
+ map lc, map {
+ s{([a-z0-9])([A-Z])}{${1}_${2}}g;
+ $_;
+ } split m{::}, $class;
+ my $fh = $storage->open('>:utf8', $file, mkpath => 1);
+ print "Writing $file\n";
+ print $fh encode_json($gathered->{$class});
+ push @files, $storage->file($file);
+ }
+ $self->_cleanup($storage, [@files]);
+ $storage->commit;
}
- $self->_cleanup(\@files);
return 1;
}
sub _cleanup {
- my ($self, $known_files) = @_;
+ my ($self, $storage, $known_files) = @_;
my %known = map { ($_ => 1) } @$known_files;
- my $data_dir = $self->storage->file(node => $self->node_path);
- my @files = $self->storage->find_files('json', node => $self->node_path);
+ my @files = $storage->find_files('json');
for my $file (@files) {
next if $known{$file};
print "Removing $file\n";
my ($self, %arg) = @_;
return System::Introspector::Gatherer->new_from_spec(
user => $self->user,
- host => $self->host,
+ host => $arg{host},
sudo_user => $arg{sudo} && $self->sudo_user,
);
}