* Created Storage->update_file() that will update a file _if_ the contents have changed, locally or remotely and log the diff of the changes that triggered the update.

* Got tools/anvil-manage-install-target to generate and record the PXE BIOS 'default' file from template.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 6 years ago
parent 9fe774071e
commit 380b11ce55
  1. 1
      Anvil/Tools.pm
  2. 158
      Anvil/Tools/Storage.pm
  3. 1
      rpm/SPECS/anvil.spec
  4. 12
      share/words.xml
  5. 20
      tools/anvil-manage-install-target

@ -895,6 +895,7 @@ sub _set_paths
'journald_anvil' => "/etc/systemd/journald.conf.d/anvil.conf", 'journald_anvil' => "/etc/systemd/journald.conf.d/anvil.conf",
'pg_hba.conf' => "/var/lib/pgsql/data/pg_hba.conf", 'pg_hba.conf' => "/var/lib/pgsql/data/pg_hba.conf",
'postgresql.conf' => "/var/lib/pgsql/data/postgresql.conf", 'postgresql.conf' => "/var/lib/pgsql/data/postgresql.conf",
pxe_default => "/var/lib/tftpboot/pxelinux.cfg/default",
ssh_config => "/etc/ssh/ssh_config", ssh_config => "/etc/ssh/ssh_config",
}, },
data => { data => {

@ -7,6 +7,8 @@ use strict;
use warnings; use warnings;
use Data::Dumper; use Data::Dumper;
use Scalar::Util qw(weaken isweak); use Scalar::Util qw(weaken isweak);
use Text::Diff;
use utf8;
our $VERSION = "3.0.0"; our $VERSION = "3.0.0";
my $THIS_FILE = "Storage.pm"; my $THIS_FILE = "Storage.pm";
@ -26,6 +28,7 @@ my $THIS_FILE = "Storage.pm";
# rsync # rsync
# search_directories # search_directories
# update_config # update_config
# update_file
# write_file # write_file
# _create_rsync_wrapper # _create_rsync_wrapper
@ -1970,6 +1973,161 @@ sub update_config
return($error); return($error);
} }
=head2 update_file
This reads in a file (if it already exists), compares it against a new body and updates it if there is a difference. This can work on remote files as well as local ones.
C<< 0 >> is returns on success (either the new file was written or the old file was not changed). Any problem will cause C<< 1 >> to be returned.
Parameters;
=head3 body (optional)
This is the new body of the file. It should always be set, of course, but it is optional in case the new file is supposed to be empty.
=head3 file (required)
This is the full path and file name of the file being updated.
=head3 port (optional, default 22)
If C<< target >> is set, this is the TCP port number used to connect to the remote machine.
=head3 password (optional)
If C<< target >> is set, this is the password used to log into the remote system as the C<< remote_user >>. If it is not set, an attempt to connect without a password will be made (though this will usually fail).
=head3 remote_user (optional, default root)
If C<< target >> is set, this is the user account that will be used when connecting to the remote system.
=head3 secure (optional)
If set to 'C<< 1 >>', the C<< body >> is treated as containing secure data for logging purposes.
=head3 target (optional)
If set, the config file will be updated on the target machine. This must be either an IP address or a resolvable host name.
=cut
sub update_file
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
my $body = defined $parameter->{body} ? $parameter->{body} : "";
my $file = defined $parameter->{file} ? $parameter->{file} : "";
my $password = defined $parameter->{password} ? $parameter->{password} : "";
my $port = defined $parameter->{port} ? $parameter->{port} : 22;
my $secure = defined $parameter->{secure} ? $parameter->{secure} : "";
my $target = defined $parameter->{target} ? $parameter->{target} : "";
my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root";
my $update = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 0, list => {
body => ((not $body) or ($anvil->Log->secure)) ? $body : $anvil->Words->string({key => "log_0186"}),
file => $file,
password => $anvil->Log->secure ? $password : $anvil->Words->string({key => "log_0186"}),
port => $port,
secure => $secure,
target => $target,
remote_user => $remote_user,
}});
if (not $file)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->update_file()", parameter => "file" }});
return(1);
}
# Read the old file...
my $old_body = $anvil->Storage->read_file({
debug => $debug,
file => $file,
password => $password,
port => $port,
target => $target,
remote_user => $remote_user,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => $secure, list => { old_body => $old_body }});
if ($old_body eq "!!error!!")
{
# File doesn't exist? Lets try writing it.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0228", variables => { file => $file }});
$update = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 0, list => { update => $update }});
}
elsif ($old_body ne $body)
{
# Something has changed. If we can get a reasonable diff, we'll show it.
# Credit: https://stackoverflow.com/questions/2047996/how-can-i-guess-if-a-string-has-text-or-binary-data-in-perl
my $is_utf8 = utf8::is_utf8($old_body);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 0, list => { is_utf8 => $is_utf8 }});
if (($is_utf8) or ($old_body =~ m/\A [[:ascii:]]* \Z/xms))
{
# $old_body is a text, so we're likely looking at a text file and can Diff it.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0230", variables => {
file => $file,
diff => diff \$old_body, \$body, { STYLE => 'Unified' },
}});
}
else
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0229", variables => { file => $file }});
}
$update = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 0, list => { update => $update }});
# Backup the file now.
my $backup_file = $anvil->Storage->backup({
file => $file,
debug => $debug,
target => $target,
port => $port,
user => $remote_user,
password => $password,
remote_user => $remote_user,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { backup_file => $backup_file }});
}
else
{
# Update not needed.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0231", variables => { file => $file }});
}
# Update/write?
if ($update)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0232", variables => { file => $file }});
my $return = $anvil->Storage->write_file({
body => $body,
debug => $debug,
file => $file,
overwrite => 1,
secure => $secure,
target => $target,
port => $port,
user => $remote_user,
password => $password,
remote_user => $remote_user,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'return' => $return }});
if ($return)
{
# Something went wrong.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0233", variables => { file => $file, 'return' => $return }});
return(1);
}
}
return(0);
}
=head2 write_file =head2 write_file
This writes out a file, either locally or on a remote system. It can optionally set the ownership and mode as well. This writes out a file, either locally or on a remote system. It can optionally set the ownership and mode as well.

@ -47,6 +47,7 @@ Requires: perl-Net-SSH2
Requires: perl-NetAddr-IP Requires: perl-NetAddr-IP
Requires: perl-Proc-Simple Requires: perl-Proc-Simple
Requires: perl-Sys-Syslog Requires: perl-Sys-Syslog
Requires: perl-Text-Diff
Requires: perl-Time-HiRes Requires: perl-Time-HiRes
Requires: perl-UUID-Tiny Requires: perl-UUID-Tiny
Requires: perl-XML-Simple Requires: perl-XML-Simple

@ -456,6 +456,17 @@ The database connection error was:
<key name="log_0225">power off</key> <key name="log_0225">power off</key>
<key name="log_0226">reboot</key> <key name="log_0226">reboot</key>
<key name="log_0227">Delay complete, proceeding with the #!variable!task!# operation now.</key> <key name="log_0227">Delay complete, proceeding with the #!variable!task!# operation now.</key>
<key name="log_0228">Failed to read the file: [#!variable!file!#]. It might not exist, so we will try to write it now.</key>
<key name="log_0229">The body of the file: [#!variable!file!#] does not match the new body. The file will be updated.</key>
<key name="log_0230">
The body of the file: [#!variable!file!#] does not match the new body. The file will be updated. The changes are:
==========
#!variable!diff!#
==========
</key>
<key name="log_0231">The file: [#!variable!file!#] is already the same as the passed in body, so no update is needed.</key>
<key name="log_0232">The file: [#!variable!file!#] will now be updated.</key>
<key name="log_0233">There was a problem updating file: [#!variable!file!#], expected the write to return '0' but got: [#!variable!return!#]. Please check the logs for details.</key>
<!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. --> <!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. -->
<key name="t_0000">Test</key> <key name="t_0000">Test</key>
@ -675,6 +686,7 @@ The update appears to have not completed successfully. The output was:
<key name="error_0040">Logging out failed. The user's UUID wasn't passed and 'sys::users::user_uuid' wasn't set. Was the user already logged out?</key> <key name="error_0040">Logging out failed. The user's UUID wasn't passed and 'sys::users::user_uuid' wasn't set. Was the user already logged out?</key>
<key name="error_0041">Failed to install the Alteeve repo, unable to proceed.</key> <key name="error_0041">Failed to install the Alteeve repo, unable to proceed.</key>
<key name="error_0042">No BCN IP address found. Unable to configure the install target feature yet.</key> <key name="error_0042">No BCN IP address found. Unable to configure the install target feature yet.</key>
<key name="error_0043">Failed to write or update the file: [#!variable!file!#]. Please see the system log for more information.</key>
<!-- These are units, words and so on used when displaying information. --> <!-- These are units, words and so on used when displaying information. -->
<key name="unit_0001">Yes</key> <key name="unit_0001">Yes</key>

@ -14,6 +14,7 @@
# 0 = Normal exit. # 0 = Normal exit.
# 1 = Alteeve repo not installed and not installable. # 1 = Alteeve repo not installed and not installable.
# 2 = BCN IP not found, unable to configure yet. # 2 = BCN IP not found, unable to configure yet.
# 3 = Failed to write or update a configuration file.
# #
# TODO: # TODO:
# - Support building the install target by mounting the ISO and checking /mnt/shared/incoming for needed # - Support building the install target by mounting the ISO and checking /mnt/shared/incoming for needed
@ -102,12 +103,27 @@ sub setup_boot_environment
$anvil->nice_exit({code => 2}); $anvil->nice_exit({code => 2});
} }
# Build and update the PXE BIOS 'default' menu.
my $base_url = "http://".$bcn_ip."/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}; my $base_url = "http://".$bcn_ip."/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { base_url => $base_url }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { base_url => $base_url }});
# Setup the BIOS boot menu. # Setup the BIOS boot menu.
my $bios_default_file = $anvil->Template->get({file => "pxe.txt", show_name => 0, name => "tftp_bios", variables => { base_url => $base_url }}); my $bios_default_body = $anvil->Template->get({file => "pxe.txt", show_name => 0, name => "tftp_bios", variables => { base_url => $base_url }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bios_default_file => $bios_default_file }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bios_default_body => $bios_default_body }});
# Read in the existing default file, if it exists, and compare it to the one we just generated.
my $bios_default_return_code = $anvil->Storage->update_file({
debug => 3,
body => $bios_default_body,
file => $anvil->data->{path}{configs}{pxe_default},
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bios_default_body => $bios_default_body }});
if ($bios_default_return_code)
{
# Failed.
print $anvil->Words->string({key => "error_0043", variables => { file => $anvil->data->{path}{configs}{pxe_default} }})."\n";
$anvil->nice_exit({code => 3});
}
die; die;

Loading…
Cancel
Save