# -*- perl -*-
# vim: syntax=perl

# $Id: edithost.plm,v 1.13 2008/07/21 17:17:36 timc Exp $

# Generate a description file from an existing system

# $Log: edithost.plm,v $
# Revision 1.13  2008/07/21 17:17:36  timc
# added some debugging with regard to the locking
#
# Revision 1.12  2007/08/31 14:54:28  thomas
# pass delete on to Allhosts
#
# Revision 1.11  2006/04/11 14:33:44  thomas
# skip updates when no change to attributes
#
# Revision 1.10  2005/10/10 17:53:37  thomas
# Allow edits iff machine status and OS agree
#
# Revision 1.9  2005/09/21 14:06:58  thomas
# cosmetic changes
#
# Revision 1.8  2005/09/14 20:05:47  thomas
# only allow edits for appropriate OSs
#
# Revision 1.7  2005/02/21 20:56:31  thomas
# Help out the stinking pile known as help
#
# Revision 1.6  2005/01/10 15:02:54  thomas
# Allow editing multiple hosts in a single invocation
#
# Revision 1.5  2004/11/05 16:19:30  thomas
# delete xml for deleted attributes.
#
# Revision 1.4  2004/10/13 16:01:47  thomas
# db version (db stores text, not data structure)
#
# Revision 1.3  2004/07/22 19:12:10  thomas
# use global help function
#
# Revision 1.2  2004/06/08 16:11:29  thomas
# Move attribute list culling and fixing to HostAttributes
#
# Revision 1.1  2004/06/08 14:13:15  thomas
# Command-line interface to HostAttributes
#

=head1 NAME

What::Command::edithost -- Edit host attributes

=head1 SYNOPSIS

what edithost [B<--db> I<database>] [B<--editor|-e> I<editor>] I<hostname>

what edithost [B<--db> I<database>] [B<--delete|-d>] I<hostname>

what edithost [B<--db> I<database>] [B<--file|-f> I<filename>] I<hostname>

what edithost [B<--db> I<database>] [B<--list|-l>] I<hostname>

=head1 DESCRIPTION

In the first synopsis, edit a host attribute list using editor editor
specified by the B<EDITOR> environment variable, or an alternate
editor specified on the command line.

In the other synopses, perform other editing operations.

=head1 OPTIONS

=over 4

=item B<--delete|-d>

Delete the attribute list for the give I<hostname>

=item B<--file|-f> I<filename>

Rather than running an editor, read the information for I<hostname> from
file file I<filename>.  If I<filename> is '-' (a single dash), read
the data from standard input.

=item B<--editor|-e> I<editor>

Rather than reading the I<EDITOR> environment variable, use I<editor>
as the editor program.

=item B<--db> I<database>

Update the host attribute database I<database> instead of the default
SL::DBs::csdb.

=item B<--list|-l>

List the attributes for I<hostname> rather than editing them.

=back

=head1 SEE ALSO

What::HostAttributes

=cut

package What::Command::edithost;

use strict;
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;
use FileHandle;
use File::MkTemp;
use Net::hostent;
use Socket;

use base qw(What::Command);
use What::HostAttributes;
use What::Csdb;
use What::AllHosts;
use What::OsMap;
use What::MachineStatus;

our $helpmsg = "Edit host attributes";

###############################################################################

sub main {
  my $self = shift;
  my $what = shift;
  local (@ARGV) = @_;

  my %opts = (
	      debug => $::opts{debug},
	     );
  my ($res, $help, $editor, $delete, $file, $list)
    = (0, 0, undef, undef, undef, undef);

  my (@lines, @attrs, $attr, $attrs, $host);
  my ($csdb, $map, $Stat, $status, $href, $os_attrs);
  my ($fh, $tmpfile, $string, $exitval, $h, $ret);

  Getopt::Long::Configure ("bundling");
  $res = GetOptions (
		     'help'       => \$help,
		     'delete|d'   => \$delete,
		     'editor|e=s' => \$editor,
		     'file|f=s'   => \$file,
		     'list|l'     => \$list,
		     'db=s'       => \$opts{db},
		    );

  $res or $what->err ("Bad option passed to edithost command!\n");

  ($help || !$res) and return $self->help;

  if (!@ARGV) {
    $what->err ("A host name is required.\n");
    return $self->help;
  }

  defined ($attrs = new What::HostAttributes (%opts)) or return 0;
  defined ($editor) or $editor = $ENV{EDITOR};
  defined ($editor) or $editor = 'vi';

  # Iterate over all hosts listed
  foreach $host (@ARGV) {

    # Check name against DNS
    $h = gethost ($host);
    if (!defined $h) {
      if (!$what->yesno (0, "$host not found in dns.  Really edit $host")) {
	next;
      }
    } elsif ($h->name ne $host) {
      if ($what->yesno (1, "Use " . $h->name)) {
	$host = $h->name;
      } elsif (!$what->yesno (0, "Really use $host" )) {
	next;
      }
    }

    # List attributes?
    if ($list) {
      $string = $attrs->find ($host);
      if (defined ($string) && length ($string)) {
	print $string . "\n";
      }
      next;
    }

    # Delete the data for the host if we're supposed to.
    if (defined $delete) {
      $res = $attrs->lock ($host);
      $res = $attrs->drop ($host);
      $res = $self->rewrite ($what, $host, 1);
      next;
    }

    # At this point, we may change the host attributes for the host.
    # Check csdb and make sure the OS says that's OK.

    $csdb = new What::Csdb unless defined $csdb;
    die "Can't make csdb connection" unless defined $csdb;
    if (!defined ($href = $csdb->host_lookup ($host))) {
      print "No csdb entry found for $host. Skipping...\n";
      next;
    }

    # Check that the OS uses per-host attributes
    $map = new What::OsMap unless defined $map;
    die "Can't map OS names" unless defined $map;
    $os_attrs = $map->get_attrs (csdb => $href->{os});
    if (!defined ($os_attrs)
	|| !defined ($os_attrs->{HostAttrType})
	|| $os_attrs->{HostAttrType} ne 'Required') {
      print "Host attributes are forbidden for OS $href->{os} on $host.\n";
      print "Skipping...\n";
      next;
    }

    # Check that the machine status is also correct
    $Stat = new What::MachineStatus unless defined $Stat;
    die "Can't lookup machine statuses!" unless defined $Stat;
    $status = $Stat->find ($href->{status}, 'SaveHostAttributes');
    if (!$status) {
      print "Host attributes are forbidden for status $href->{status} on $host.\n";
      print "Skipping...\n";
      next;
    }

    # All clear to change host attributes for this host...

    # Get the old attributes for the host
    $what->debug (3, 'Creating database lock for %s%s', $host, "\n");
    $string = $attrs->lock ($host);
    if (!defined ($string) || !length ($string)) {
      $string = "# Host attributes for $host\n";
    }

    # Read the data from a file
    if (defined $file) {
      # Have to lock first
      $what->debug (3, 'lock obtained, apply_attrs_from_file next.\n');
      $self->apply_attrs_from_file ($what, $attrs, $host, $file, $string);
      next;
    }

    # write out the file to edit and run the editor
    ($fh, $tmpfile) = mkstempt ('whatXXXXXX', '/tmp');
    print $fh $string;
    close $fh;
    $exitval = system ($editor, "/tmp/$tmpfile") >> 8;
    if ($exitval) {
      $what->err ( "%s returned %d -- not updating data\n", $editor, $exitval);
      next;
    }

    $ret = $self->apply_attrs_from_file ($what, $attrs, $host, "/tmp/$tmpfile", $string);
    unlink "/tmp/$tmpfile";
  }

  return 0;
}


###############################################################################
# Read the contents of the file as the new data for the host
sub apply_attrs_from_file {
  my $self = shift;
  my $what = shift;
  my $attrs = shift;  # Host attribute db handle
  my $host = shift;
  my $file = shift;
  my $oldtext = shift; # Old text - don't allhosts if no change

  my ($fh, $text);

  if (!defined ($fh = new FileHandle $file, 'r')) {
    $what->err ("Failed to open %s for reading!\n", $file);
    return 0;
  }
  $text = join ('', $fh->getlines);
  close $fh;

  if ($text eq $oldtext) {
    print "No change in host attributes for $host.  Skipping updates.\n";
    $attrs->unlock;
    return 0;
  }
  $attrs->set ($host, $text);
  $self->rewrite ($what, $host, 0);
  return 0;
}

###############################################################################

sub rewrite {
  my $self = shift;
  my $what = shift;
  my $host = shift;
  my $delete = shift;

  # Re-run all the allhosts bits for this host
  my $allhosts_options = $delete ? {delete => 1} : {};
  my $allhosts_modules = [];
  my $allhosts_hosts   = [$host];
  What::AllHosts->do (
			      $what,
			      $allhosts_options,
			      $allhosts_modules,
			      $allhosts_hosts,
			     )

}

###############################################################################

1;

