* Created the new tools/striker-purge-host tool which purges all data about a host from the database.

* Updated Database->get_hosts() to store 'host_key' and 'host_uuid' data.
* Created Database->get_ssh_keys().
* Fixed a couple bugs where Get->host_type() now returns 'striker' but tests checked for 'dashboard'.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 5 years ago
parent 0b2eb88f39
commit 7d907c15ff
  1. 145
      Anvil/Tools/Database.pm
  2. 42
      Anvil/Tools/System.pm
  3. 11
      share/words.xml
  4. 324
      tools/striker-purge-host
  5. 2
      tools/test.pl

@ -32,6 +32,7 @@ my $THIS_FILE = "Database.pm";
# get_manifests # get_manifests
# get_notifications # get_notifications
# get_recipients # get_recipients
# get_ssh_keys
# get_upses # get_upses
# initialize # initialize
# insert_or_update_anvils # insert_or_update_anvils
@ -293,6 +294,8 @@ sub archive_database
This checks to see if 'sys::database::local_lock_active' is set. If it is, its age is checked and if the age is >50% of sys::database::locking_reap_age, it will renew the lock. This checks to see if 'sys::database::local_lock_active' is set. If it is, its age is checked and if the age is >50% of sys::database::locking_reap_age, it will renew the lock.
This method takes no parameters.
=cut =cut
sub check_lock_age sub check_lock_age
{ {
@ -357,6 +360,8 @@ If the system is already configured, this method will do nothing, so it is safe
If the method completes, C<< 0 >> is returned. If this method is called without C<< root >> access, it returns C<< 1 >> without doing anything. If there is a problem, C<< !!error!! >> is returned. If the method completes, C<< 0 >> is returned. If this method is called without C<< root >> access, it returns C<< 1 >> without doing anything. If there is a problem, C<< !!error!! >> is returned.
This method takes no parameters.
=cut =cut
sub configure_pgsql sub configure_pgsql
{ {
@ -1332,7 +1337,7 @@ sub connect
$anvil->Database->mark_active({debug => $debug, set => 1}); $anvil->Database->mark_active({debug => $debug, set => 1});
# Sync the database, if needed. # Sync the database, if needed.
$anvil->Database->resync_databases({debug => 3}); $anvil->Database->resync_databases({debug => $debug});
# Add ourselves to the database, if needed. # Add ourselves to the database, if needed.
$anvil->Database->insert_or_update_hosts({debug => $debug}); $anvil->Database->insert_or_update_hosts({debug => $debug});
@ -1346,6 +1351,8 @@ sub connect
This cleanly closes any open file handles to all connected databases and clears some internal database related variables. This cleanly closes any open file handles to all connected databases and clears some internal database related variables.
This method takes no parameters.
=cut =cut
sub disconnect sub disconnect
{ {
@ -1393,6 +1400,8 @@ sub disconnect
This returns a list of users listening to alerts for a given host, along with their alert level. This returns a list of users listening to alerts for a given host, along with their alert level.
This method takes no parameters.
=cut =cut
sub get_recipients sub get_recipients
{ {
@ -1402,11 +1411,6 @@ sub get_recipients
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->get_recipients()" }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->get_recipients()" }});
my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : $anvil->Get->host_uuid;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
host_uuid => $host_uuid,
}});
### TODO: Read in 'notifications' ### TODO: Read in 'notifications'
my $query = " my $query = "
SELECT SELECT
@ -1814,12 +1818,16 @@ Each anonymous hash is structured as:
host_uuid => $host_uuid, host_uuid => $host_uuid,
host_name => $host_name, host_name => $host_name,
host_type => $host_type, host_type => $host_type,
host_key => $host_key,
host_ipmi => $host_ipmi,
modified_date => $modified_date, modified_date => $modified_date,
It also sets the variables; It also sets the variables;
hosts::host_uuid::<host_uuid>::host_name = <host_name>; hosts::host_uuid::<host_uuid>::host_name = <host_name>;
hosts::host_uuid::<host_uuid>::host_type = <host_type; node, dr or dashboard> hosts::host_uuid::<host_uuid>::host_type = <host_type; node, dr or dashboard>
hosts::host_uuid::<host_uuid>::host_key = <Machine's public key / fingerprint>
hosts::host_uuid::<host_uuid>::host_ipmi = <If equiped, this is how to log into the host's IPMI BMC>
hosts::host_uuid::<host_uuid>::anvil_name = <anvil_name if associated with an anvil> hosts::host_uuid::<host_uuid>::anvil_name = <anvil_name if associated with an anvil>
And to simplify look-ups by UUID or name; And to simplify look-ups by UUID or name;
@ -1829,6 +1837,8 @@ And to simplify look-ups by UUID or name;
To prevent some cases of recursion, C<< hosts::loaded >> is set on successful load, and if this is set, this method immediately returns with C<< 0 >>. To prevent some cases of recursion, C<< hosts::loaded >> is set on successful load, and if this is set, this method immediately returns with C<< 0 >>.
This method takes no parameters.
=cut =cut
sub get_hosts sub get_hosts
{ {
@ -1838,6 +1848,11 @@ sub get_hosts
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->get_hosts()" }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->get_hosts()" }});
# Delete any data from past scans.
delete $anvil->data->{hosts}{host_uuid};
delete $anvil->data->{sys}{hosts}{by_uuid};
delete $anvil->data->{sys}{hosts}{by_name};
# Load anvils. If a host is registered with an Anvil!, we'll note it. # Load anvils. If a host is registered with an Anvil!, we'll note it.
$anvil->Database->get_anvils({debug => $debug}); $anvil->Database->get_anvils({debug => $debug});
@ -1846,6 +1861,8 @@ SELECT
host_uuid, host_uuid,
host_name, host_name,
host_type, host_type,
host_key,
host_ipmi,
modified_date modified_date
FROM FROM
hosts hosts
@ -1861,14 +1878,18 @@ FROM
}}); }});
foreach my $row (@{$results}) foreach my $row (@{$results})
{ {
my $host_uuid = $row->[0]; my $host_uuid = $row->[0];
my $host_name = $row->[1]; my $host_name = $row->[1];
my $host_type = $row->[2]; my $host_type = defined $row->[2] ? $row->[2] : "";
my $modified_date = $row->[3]; my $host_key = defined $row->[3] ? $row->[3] : "";
my $host_ipmi = $row->[4];
my $modified_date = $row->[5];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
host_uuid => $host_uuid, host_uuid => $host_uuid,
host_name => $host_name, host_name => $host_name,
host_type => $host_type, host_type => $host_type,
host_key => $host_key,
host_ipmi => $host_ipmi,
modified_date => $modified_date, modified_date => $modified_date,
}}); }});
@ -1883,16 +1904,22 @@ FROM
host_uuid => $host_uuid, host_uuid => $host_uuid,
host_name => $host_name, host_name => $host_name,
host_type => $host_type, host_type => $host_type,
host_key => $host_key,
host_ipmi => $host_ipmi,
modified_date => $modified_date, modified_date => $modified_date,
}; };
# Record the data in the hash, too. # Record the data in the hash, too.
$anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name} = $host_name; $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name} = $host_name;
$anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type} = $host_type; $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type} = $host_type;
$anvil->data->{hosts}{host_uuid}{$host_uuid}{host_key} = $host_key;
$anvil->data->{hosts}{host_uuid}{$host_uuid}{host_ipmi} = $host_ipmi;
$anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name} = $anvil_name; $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name} = $anvil_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"hosts::host_uuid::${host_uuid}::host_name" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name}, "hosts::host_uuid::${host_uuid}::host_name" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name},
"hosts::host_uuid::${host_uuid}::host_type" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type}, "hosts::host_uuid::${host_uuid}::host_type" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type},
"hosts::host_uuid::${host_uuid}::host_key" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_key},
"hosts::host_uuid::${host_uuid}::host_ipmi" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_ipmi},
"hosts::host_uuid::${host_uuid}::anvil_name" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name}, "hosts::host_uuid::${host_uuid}::anvil_name" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name},
}}); }});
@ -1915,6 +1942,8 @@ FROM
This gathers up all the known information about all known hosts. This gathers up all the known information about all known hosts.
This method takes no parameters.
=cut =cut
sub get_hosts_info sub get_hosts_info
{ {
@ -2010,6 +2039,8 @@ Parameters;
This is the C<< job_uuid >> of the job being retrieved. This is the C<< job_uuid >> of the job being retrieved.
This method takes no parameters.
=cut =cut
sub get_job_details sub get_job_details
{ {
@ -2132,6 +2163,8 @@ Jobs that reached 100% within this number of seconds ago will be included. If th
This is the host that we're getting a list of jobs from. This is the host that we're getting a list of jobs from.
This method takes no parameters.
=cut =cut
sub get_jobs sub get_jobs
{ {
@ -2270,6 +2303,8 @@ NOTE: This returns nothing if the local machine is not found as a configured dat
# Get the local UUID # Get the local UUID
my $local_uuid = $anvil->Database->get_local_uuid; my $local_uuid = $anvil->Database->get_local_uuid;
This method takes no parameters.
=cut =cut
sub get_local_uuid sub get_local_uuid
{ {
@ -2340,6 +2375,8 @@ sub get_local_uuid
This gets the list of configured mail servers. This gets the list of configured mail servers.
This method takes no parameters.
=cut =cut
sub get_mail_servers sub get_mail_servers
{ {
@ -2632,6 +2669,94 @@ FROM
} }
=head2 get_ssh_keys
This loads all known user's SSH public keys and all known machine's public keys into the data hash. On success, this method returns C<< 0 >>. If any problems occur, C<< 1 >> is returned.
ssh_keys::ssh_key_uuid::<ssh_key_uuid>::ssh_key_host_uuid = <Host UUID the user is from>
ssh_keys::ssh_key_uuid::<ssh_key_uuid>::ssh_key_user_name = <The user's name>
ssh_keys::ssh_key_uuid::<ssh_key_uuid>::ssh_key_public_key = <The SSH public key>
And:
ssh_keys::ssh_key_host_uuid::<ssh_key_host_uuid>::ssh_key_uuid = <UUID of ssh_keys column>
ssh_keys::ssh_key_host_uuid::<ssh_key_host_uuid>::ssh_key_user_name = <The user's name>
ssh_keys::ssh_key_host_uuid::<ssh_key_host_uuid>::ssh_key_public_key = <The SSH public key>
This method takes no parameters.
=cut
sub get_ssh_keys
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->initialize()" }});
# Delete any data from past scans.
delete $anvil->data->{ssh_keys}{ssh_key_uuid};
delete $anvil->data->{sys}{ssh_keys}{by_uuid};
delete $anvil->data->{sys}{ssh_keys}{by_name};
my $query = "
SELECT
ssh_key_uuid,
ssh_key_host_uuid,
ssh_key_user_name,
ssh_key_public_key,
modified_date
FROM
ssh_keys
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
results => $results,
count => $count,
}});
foreach my $row (@{$results})
{
my $ssh_key_uuid = $row->[0];
my $ssh_key_host_uuid = $row->[1];
my $ssh_key_user_name = $row->[2];
my $ssh_key_public_key = $row->[3];
my $modified_date = $row->[4];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
ssh_key_uuid => $ssh_key_uuid,
ssh_key_host_uuid => $ssh_key_host_uuid,
ssh_key_user_name => $ssh_key_user_name,
ssh_key_public_key => $ssh_key_public_key,
modified_date => $modified_date,
}});
# Record the data in the hash, too.
$anvil->data->{ssh_keys}{ssh_key_uuid}{$ssh_key_uuid}{ssh_key_host_uuid} = $ssh_key_host_uuid;
$anvil->data->{ssh_keys}{ssh_key_uuid}{$ssh_key_uuid}{ssh_key_user_name} = $ssh_key_user_name;
$anvil->data->{ssh_keys}{ssh_key_uuid}{$ssh_key_uuid}{ssh_key_public_key} = $ssh_key_public_key;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"ssh_keys::ssh_key_uuid::${ssh_key_uuid}::ssh_key_host_uuid" => $anvil->data->{ssh_keys}{ssh_key_uuid}{$ssh_key_uuid}{ssh_key_host_uuid},
"ssh_keys::ssh_key_uuid::${ssh_key_uuid}::ssh_key_user_name" => $anvil->data->{ssh_keys}{ssh_key_uuid}{$ssh_key_uuid}{ssh_key_user_name},
"ssh_keys::ssh_key_uuid::${ssh_key_uuid}::ssh_key_public_key" => $anvil->data->{ssh_keys}{ssh_key_uuid}{$ssh_key_uuid}{ssh_key_public_key},
}});
$anvil->data->{ssh_keys}{ssh_key_host_uuid}{$ssh_key_host_uuid}{ssh_key_uuid} = $ssh_key_uuid;
$anvil->data->{ssh_keys}{ssh_key_host_uuid}{$ssh_key_host_uuid}{ssh_key_user_name} = $ssh_key_user_name;
$anvil->data->{ssh_keys}{ssh_key_host_uuid}{$ssh_key_host_uuid}{ssh_key_public_key} = $ssh_key_public_key;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"ssh_keys::ssh_key_host_uuid::${ssh_key_host_uuid}::ssh_key_uuid" => $anvil->data->{ssh_keys}{ssh_key_host_uuid}{$ssh_key_host_uuid}{ssh_key_uuid},
"ssh_keys::ssh_key_host_uuid::${ssh_key_host_uuid}::ssh_key_user_name" => $anvil->data->{ssh_keys}{ssh_key_host_uuid}{$ssh_key_host_uuid}{ssh_key_user_name},
"ssh_keys::ssh_key_host_uuid::${ssh_key_host_uuid}::ssh_key_public_key" => $anvil->data->{ssh_keys}{ssh_key_host_uuid}{$ssh_key_host_uuid}{ssh_key_public_key},
}});
}
return(0);
}
=head2 get_upses =head2 get_upses
This loads the known UPSes (uninterruptible power supplies) into the C<< anvil::data >> hash at: This loads the known UPSes (uninterruptible power supplies) into the C<< anvil::data >> hash at:

@ -22,6 +22,7 @@ my $THIS_FILE = "System.pm";
# change_shell_user_password # change_shell_user_password
# check_daemon # check_daemon
# check_if_configured # check_if_configured
# check_ssh_keys
# check_memory # check_memory
# check_storage # check_storage
# disable_daemon # disable_daemon
@ -643,8 +644,9 @@ sub check_ssh_keys
my $users = $anvil->Get->host_type eq "node" ? ["root", "admin", "hacluster"] : ["root", "admin"]; my $users = $anvil->Get->host_type eq "node" ? ["root", "admin", "hacluster"] : ["root", "admin"];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { users => \@{$users} }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { users => \@{$users} }});
# Get a list of machine host keys and user public keys from other machines. # Load the host keys and the SSH keys
#get_other_keys($anvil); $anvil->Database->get_hosts({debug => $debug});
$anvil->Database->get_ssh_keys({debug => $debug});
# Users to check: # Users to check:
foreach my $user (@{$users}) foreach my $user (@{$users})
@ -672,11 +674,6 @@ sub check_ssh_keys
group => $user, group => $user,
mode => "0700", mode => "0700",
}); });
if (not -e $ssh_directory)
{
# Failed ?
next;
}
} }
my $ssh_private_key_file = $user_home."/.ssh/id_rsa"; my $ssh_private_key_file = $user_home."/.ssh/id_rsa";
@ -753,10 +750,33 @@ sub check_ssh_keys
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { authorized_keys_file_body => $authorized_keys_file_body }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { authorized_keys_file_body => $authorized_keys_file_body }});
} }
### Walk through each host we now know of. As we we do, loop through the old file body to see # Walk through the Striker dashboards we use. If we're a Node or DR host, walk through our
### if it exists. If it does, and the key has changed, update the line with the new key. If # peers as well. As we we do, loop through the old file body to see if it exists. If it does,
### it isn't found, add it. Once we check the old body for this entry, change the "old" body # and the key has changed, update the line with the new key. If it isn't found, add it. Once
### to the new one, then repeat the process. # we check the old body for this entry, change the "old" body to the new one, then repeat the
# process.
my $trusted_host_uuids = [];
if ($anvil->Get->host_type eq "striker")
{
# Add all known machines
}
else
{
# Add dashboard (using postgres connection info), the UUID is the host UUID.
foreach my $uuid (sort {$a cmp $b} keys %{$anvil->data->{database}})
{
# Periodically, autovivication causes and empty key to appear.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { uuid => $uuid }});
next if ((not $uuid) or (not $anvil->Validate->is_uuid({uuid => $uuid})));
push @{$trusted_host_uuids}, $uuid;
}
# Now add our peers.
}
my $peers = [];
# Look at all the hosts I know about (other than myself) and see if any of the machine or # Look at all the hosts I know about (other than myself) and see if any of the machine or
# user keys either don't exist or have changed. # user keys either don't exist or have changed.

@ -33,6 +33,7 @@ Author: Madison Kelly <mkelly@alteeve.ca>
<key name="brand_0006"><![CDATA[<i>Anvil!</i>]]></key> <key name="brand_0006"><![CDATA[<i>Anvil!</i>]]></key>
<key name="brand_0007">Node</key> <key name="brand_0007">Node</key>
<key name="brand_0008">DR Host</key> <key name="brand_0008">DR Host</key>
<key name="brand_0009">Unknown Type</key>
<!-- Errors --> <!-- Errors -->
<key name="error_0001">There are not enough network interfaces on this machine. You have: [#!variable!interface_count!#] interface(s), and you need at least: [#!variable!required_interfaces_for_single!#] interfaces to connect to the requested networks (one for Back-Channel and one for each Internet-Facing network).</key> <key name="error_0001">There are not enough network interfaces on this machine. You have: [#!variable!interface_count!#] interface(s), and you need at least: [#!variable!required_interfaces_for_single!#] interfaces to connect to the requested networks (one for Back-Channel and one for each Internet-Facing network).</key>
@ -188,6 +189,8 @@ The error was:
<key name="error_0127">I was asked to delete and entry from: [#!variable!table!#] but neither the name or UUID was passed.</key> <key name="error_0127">I was asked to delete and entry from: [#!variable!table!#] but neither the name or UUID was passed.</key>
<key name="error_0128">The host UUID: [#!variable!uuid!#] was set as the value for: [#!variable!column!#], but that host doesn't appear to exist.</key> <key name="error_0128">The host UUID: [#!variable!uuid!#] was set as the value for: [#!variable!column!#], but that host doesn't appear to exist.</key>
<key name="error_0129">Unable to connect to any database, unable to read the job details.</key> <key name="error_0129">Unable to connect to any database, unable to read the job details.</key>
<key name="error_0130">The answer: [#!variable!answer!#] is invalid. Please try again.</key>
<key name="error_0131">The host UUID: [#!variable!host_uuid!#] was not found. Has it already been purged?</key>
<!-- Table headers --> <!-- Table headers -->
<key name="header_0001">Current Network Interfaces and States</key> <key name="header_0001">Current Network Interfaces and States</key>
@ -1126,6 +1129,14 @@ About to try to download aproximately: [#!variable!packages!#] packages needed t
<key name="message_0166">An isolated, VLAN'ed network used for all inter-machine communication in the #!string!brand_0006!#, as well as communication for foundation pack devices.</key> <key name="message_0166">An isolated, VLAN'ed network used for all inter-machine communication in the #!string!brand_0006!#, as well as communication for foundation pack devices.</key>
<key name="message_0167">An isolated, VLAN'ed network Used for storage replication traffic only.</key> <key name="message_0167">An isolated, VLAN'ed network Used for storage replication traffic only.</key>
<key name="message_0168">Connecting to the main site intranet. This is the network (or networks) that guest virtual servers will use to connect to all devices outside the #!string!brand_0006!# system.</key> <key name="message_0168">Connecting to the main site intranet. This is the network (or networks) that guest virtual servers will use to connect to all devices outside the #!string!brand_0006!# system.</key>
<key name="message_0169">Please select the host you want to purge from the database:</key>
<key name="message_0170">#!variable!key!#) #!variable!host_name!# - #!variable!type!# - #!variable!host_uuid!#)</key>
<key name="message_0171">Which machine do you want to purge from the database(s)? </key>
<key name="message_0172">Note: Be sure all databases are online! Otherwise, the purged records could return during the next resync!</key>
<key name="message_0173">Are you sure you want to purge: [#!variable!host_name!# (#!variable!host_uuid!#)]?</key> <!-- Translation note: If you want to accept other characters as confirmation, update 'striker-purge-host', 'sub confirm' in the 'normalize -y' section. -->
<key name="message_0174">Confirmed by switch, proceeding with purge of: [#!variable!host_name!#].</key>
<key name="message_0175">Thank you, proceeding.</key>
<key name="message_0176">The host: [#!variable!host_name!#] has been purged.</key>
<!-- Success messages shown to the user --> <!-- Success messages shown to the user -->
<key name="ok_0001">Saved the mail server information successfully!</key> <key name="ok_0001">Saved the mail server information successfully!</key>

@ -0,0 +1,324 @@
#!/usr/bin/perl
#
# This tool is meant to be run from the command line and lets a user purge all information about a given host
# from the databases.
#
# Exit codes;
# 0 = Normal exit.
# 1 = User's answer to which machine to purge was invalid.
# 2 = The passed-in (or selected) host UUID (no longer) exists.
# 3 = User did not confirm to proceed.
# 4 = Runaway loop detected.
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
use Curses::UI;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete.
$| = 1;
my $anvil = Anvil::Tools->new({debug => 2});
$anvil->Log->secure({set => 1});
$anvil->Log->level({set => 2});
$anvil->Database->connect({debug => 3});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132"});
# First, ask the user to select a host.
$anvil->data->{switches}{'host-uuid'} = "" if not defined $anvil->data->{switches}{'host-uuid'};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 'switches::host-uuid' => $anvil->data->{switches}{'host-uuid'} }});
$anvil->Database->get_hosts();
# Ask the user to pick a host.
pick_host($anvil);
# Ask the user to confirm.
confirm($anvil);
# If we're alive, purge.
purge($anvil);
$anvil->nice_exit({code => 0});
#############################################################################################################
# Functions #
#############################################################################################################
sub purge
{
my ($anvil) = @_;
# Read in all constraints and create a hash of what depends on what, then delete anything referencing
# hosts without any further references. As each delete is done, delete it. Loop until all entries are
# gone.
# Load all the known tables in the DB.
my $query = "SELECT schemaname||'.'||tablename AS table_name FROM pg_catalog.pg_tables WHERE schemaname = 'history' ORDER BY tablename ASC, schemaname ASC;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
results => $results,
count => $count,
}});
foreach my $row (@{$results})
{
my $table_name = $row->[0];
$anvil->data->{sql}{history_tables}{$table_name} = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "sql::history_tables::${table_name}" => $anvil->data->{sql}{history_tables}{$table_name} }});
}
undef $query;
undef $results;
undef $count;
### NOTE: Credit for this query goes to:
### - https://stackoverflow.com/questions/1152260/postgres-sql-to-list-table-foreign-keys
$query = "
SELECT
tc.table_schema||'.'||tc.table_name AS table,
kcu.column_name,
ccu.table_schema||'.'||ccu.table_name AS foreign_table,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE
tc.constraint_type = 'FOREIGN KEY'
AND
ccu.column_name = 'host_uuid'
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
$results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
$count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
results => $results,
count => $count,
}});
foreach my $row (@{$results})
{
my $table_name = $row->[0];
my $column_name = $row->[1];
my $foreign_table_name = $row->[2];
my $foreign_column_name = $row->[3];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
's1:table_name' => $table_name,
's2:column_name' => $column_name,
's3:foreign_table_name' => $foreign_table_name,
's4:foreign_column_name' => $foreign_column_name,
}});
if (not exists $anvil->data->{sql}{table}{$table_name})
{
$anvil->data->{sql}{table}{$table_name}{column} = $column_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "sql::table::${table_name}::column" => $anvil->data->{sql}{table}{$table_name}{column} }});
}
if (not exists $anvil->data->{sql}{table}{$foreign_table_name})
{
$anvil->data->{sql}{table}{$foreign_table_name}{column} = $foreign_column_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "sql::table::${foreign_table_name}::column" => $anvil->data->{sql}{table}{$foreign_table_name}{column} }});
}
$anvil->data->{sql}{table}{$foreign_table_name}{referenced_by}{$table_name} = 1;
}
my $loops = 20;
my $loop = 0;
my $done = 0;
my $queries = [];
until($done)
{
$loop++;
foreach my $table_name (sort {$a cmp $b} keys %{$anvil->data->{sql}{table}})
{
my $count = exists $anvil->data->{sql}{table}{$table_name}{referenced_by} ? keys %{$anvil->data->{sql}{table}{$table_name}{referenced_by}} : 0;
#print "-- Table: [".$table_name."] is referenced by: [".$count."] foreign table(s);\n";
if (not $count)
{
# If there is a corresponding history table, delete it as well.
my $column = $anvil->data->{sql}{table}{$table_name}{column};
my $history_table = $table_name;
$history_table =~ s/^public\./history\./;
if (exists $anvil->data->{sql}{history_tables}{$history_table})
{
push @{$queries}, "DELETE FROM ".$history_table." WHERE ".$column." = ".$anvil->Database->quote($anvil->data->{switches}{'host-uuid'}).";";
}
# Now delete the public table entries.
push @{$queries}, "DELETE FROM ".$table_name." WHERE ".$column." = ".$anvil->Database->quote($anvil->data->{switches}{'host-uuid'}).";";
foreach my $foreign_table_name (sort {$a cmp $b} keys %{$anvil->data->{sql}{table}})
{
if (exists $anvil->data->{sql}{table}{$foreign_table_name}{referenced_by}{$table_name})
{
#print "-- Deleting: [".$foreign_table_name."] reference to: [".$table_name."]\n";
delete $anvil->data->{sql}{table}{$foreign_table_name}{referenced_by}{$table_name};
}
}
#print "-- Deleting hash entry for: [".$table_name."]\n";
delete $anvil->data->{sql}{table}{$table_name};
}
}
my $remaining = keys %{$anvil->data->{sql}{table}};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { remaining => $remaining }});
if (not $remaining)
{
$done = 1;
}
elsif ($loop > $loops)
{
print "Run-away loop detected, aborting!\n";
$anvil->nice_exit({code => 4});
}
}
# Do the deed.
$anvil->Database->write({debug => 2, query => $queries, source => $THIS_FILE, line => __LINE__});
my $host_uuid = $anvil->data->{switches}{'host-uuid'};
print $anvil->Words->string({key => "message_0176", variables => { host_name => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name} }})."\n";
return(0);
}
sub confirm
{
my ($anvil) = @_;
# Normalize -y
$anvil->data->{switches}{'y'} = "" if not defined $anvil->data->{switches}{'y'};
$anvil->data->{switches}{'yes'} = "" if not defined $anvil->data->{switches}{'yes'};
$anvil->data->{switches}{'Y'} = "" if not defined $anvil->data->{switches}{'Y'};
$anvil->data->{switches}{'Yes'} = "" if not defined $anvil->data->{switches}{'Yes'};
$anvil->data->{switches}{'YES'} = "" if not defined $anvil->data->{switches}{'YES'};
if (not $anvil->data->{switches}{'y'})
{
if ($anvil->data->{switches}{'yes'}) { $anvil->data->{switches}{'y'} = $anvil->data->{switches}{'yes'}; }
elsif ($anvil->data->{switches}{'Y'}) { $anvil->data->{switches}{'y'} = $anvil->data->{switches}{'Y'}; }
elsif ($anvil->data->{switches}{'Yes'}) { $anvil->data->{switches}{'y'} = $anvil->data->{switches}{'Yes'}; }
elsif ($anvil->data->{switches}{'YES'}) { $anvil->data->{switches}{'y'} = $anvil->data->{switches}{'YES'}; }
}
# Ask to confirm, is sane and not -y
my $host_uuid = $anvil->data->{switches}{'host-uuid'};
if (not exists $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name})
{
print $anvil->Words->string({key => "error_0131", variables => { host_uuid => $host_uuid }})."\n\n";
$anvil->nice_exit({code => 2});
}
else
{
print $anvil->Words->string({key => "message_0172"})."\n\n";
print $anvil->Words->string({key => "message_0173", variables => {
host_name => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name},
host_uuid => $host_uuid,
}})."\n";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 'switches::y' => $anvil->data->{switches}{'y'} }});
if ($anvil->data->{switches}{'y'} eq "#!SET!#")
{
# Already confirmed.
print $anvil->Words->string({key => "message_0023"})."\n\n";
return(0);
}
print $anvil->Words->string({key => "message_0021"})." ";
chomp(my $answer = <STDIN>);
if ($answer =~ /^y/i)
{
# Proceed.
print $anvil->Words->string({key => "message_0175"})."\n";
}
else
{
# Abort.
print $anvil->Words->string({key => "message_0022"})."\n";
$anvil->nice_exit({code => 3});
}
}
return(0);
}
sub pick_host
{
my ($anvil) = @_;
return(0) if $anvil->data->{switches}{'host-uuid'};
# Get a list of hosts
my $i = 1;
my $select = {};
my $host_length = 0;
my $type_length = 0;
foreach my $host_name (sort {$a cmp $b} keys %{$anvil->data->{sys}{hosts}{by_name}})
{
my $host_uuid = $anvil->data->{sys}{hosts}{by_name}{$host_name};
my $host_type = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type};
my $anvil_name = defined $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name} ? $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name} : "";
my $say_type = $anvil->Words->string({key => "brand_0009"});
if ($host_type eq "striker") { $say_type = $anvil->Words->string({key => "brand_0003"}); }
elsif ($host_type eq "node") { $say_type = $anvil->Words->string({key => "brand_0007"}); }
elsif ($host_type eq "dr") { $say_type = $anvil->Words->string({key => "brand_0008"}); }
if (length($host_name) > $host_length) { $host_length = length($host_name); }
if (length($say_type) > $type_length) { $type_length = length($say_type); }
# This is used to build the menu.
$select->{$i}{host_uuid} = $host_uuid;
$select->{$i}{host_name} = $host_name;
$select->{$i}{host_type} = $host_type;
$select->{$i}{say_type} = $say_type;
$select->{$i}{anvil_name} = $anvil_name;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"${i}::host_uuid" => $select->{$i}{host_uuid},
"${i}::host_name" => $select->{$i}{host_name},
"${i}::host_type" => $select->{$i}{host_type},
"${i}::anvil_name" => $select->{$i}{anvil_name},
}});
$i++;
}
my $pad = $i > 9 ? 2 : 1;
print $anvil->Words->string({key => "message_0169"})."\n\n";
foreach my $i (sort {$a cmp $b} keys %{$select})
{
print " ".$anvil->Words->string({key => "message_0170", variables => {
key => sprintf("%".$pad."s", $i),
type => sprintf("%-".$type_length."s", $select->{$i}{say_type}),
host_name => sprintf("%-".$host_length."s", $select->{$i}{host_name}),
host_uuid => $select->{$i}{host_uuid},
}})."\n";
}
print "\n".$anvil->Words->string({key => "message_0171"})." ";
chomp(my $answer = <STDIN>);
if (not exists $select->{$answer}{host_name})
{
print "\n".$anvil->Words->string({key => "error_0130", variables => { answer => $answer }})."\n\n";
$anvil->nice_exit({code => 1});
}
else
{
print "\n";
$anvil->data->{switches}{'host-uuid'} = $select->{$answer}{host_uuid};
}
return(0);
}

@ -25,4 +25,4 @@ $anvil->Database->connect({debug => 3});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132"}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132"});
print "DB Connections: [".$anvil->data->{sys}{database}{connections}."]\n"; print "DB Connections: [".$anvil->data->{sys}{database}{connections}."]\n";
$anvil->System->check_ssh_keys({debug => 2}); #$anvil->System->check_ssh_keys({debug => 2});

Loading…
Cancel
Save