#! /usr/bin/env perl
#
# Uses GET, PUT, and POST requests in the Infoblox RESTful Web API (WAPI) to
# either create a new Host record or add an IP to an existing Host record.
#
# Requires IPAMUserAgent.pm, JSON, and NetAddr::IP
#
# Author: dmrz
use warnings;
use strict;

sub usage {
  print STDERR <<EOF;
Usage: $0 fqdn IP

Creates a new Host record with the specified primary fqdn and IP.  If a Host
record already exists with the specified fqdn, adds IP to that Host record
instead.  If IP is already registered to another Host, aborts with a warning.

Examples:
 $0 foo.sandbox.illinois.edu 192.168.0.5
 $0 foo.sandbox.illinois.edu 2001:db8::5
EOF
  exit 1;
}

## parse command line

use Getopt::Long;
my ($USAGE);
&GetOptions
  (
   "help" => \$USAGE)
  or $USAGE = 1;
usage() if $USAGE;
my $FQDN = shift or usage();
my $IP = shift or usage();


## interesting work starts here

use IPAMUserAgent;
use JSON qw( decode_json );

my $ua = IPAMUserAgent->new;
my $WAPI = $ua->wapi;  # used in request URLs below

$ua->timeout(180);  # see LWP::UserAgent

# Comment this out if you don't want to see debugging
$ua->debug_enable(1);

add_host_ip($FQDN, $IP);



sub add_host_ip {
  my ($fqdn, $ipaddr) = @_;

  use NetAddr::IP qw(:lower);
  my $ip = NetAddr::IP->new($ipaddr) or die "'$ipaddr' is not a valid IP";

  # check if IP already has a Host
  my $httpresponse;
  if ($ip->version == 4) {
    $httpresponse = $ua->get( "$WAPI/record:host?ipv4addr=$ipaddr&_return_fields=name" );
  } else {
    # search is picky about IPv6 spelling
    $httpresponse = $ua->get( "$WAPI/record:host?ipv6addr=".$ip->short."&_return_fields=name" );
  }
  my $existing_hosts_for_ip = decode_json($httpresponse->decoded_content);
  if (@$existing_hosts_for_ip) {
    my $host = $existing_hosts_for_ip->[0];
    my $name = $host->{name};
    die "Abort: IP '$ipaddr' already in use by Host '$name', creating another Host would result in duplicate PTRs";
  }

  # check if Host already exists (note ':' for case-insensitive search)
  my $existing_hosts_for_fqdn = decode_json
    $ua->get( "$WAPI/record:host?name:=$fqdn" )
      ->decoded_content;
  if (@$existing_hosts_for_fqdn) {
    # use PUT to modify the existing Host record (note special '+' behavior
    # supported by these fields to add a new IP without altering others)
    my $hostref = $existing_hosts_for_fqdn->[0]->{_ref};
    if ($ip->version == 4) {
      $ua->put( "$WAPI/$hostref",
                "Content-Type" => "application/json",
                Content => qq| { "ipv4addrs+": [{"ipv4addr": "$ipaddr"}] } |);
    } else {
      $ua->put( "$WAPI/$hostref",
                "Content-Type" => "application/json",
                Content => qq| { "ipv6addrs+": [{"ipv6addr": "$ipaddr"}] } |);
    }
    print "Added IP '$ipaddr' to Host '$fqdn'\n";
  } else {
    # use POST to create a new Host record
    if ($ip->version == 4) {
      $ua->post( "$WAPI/record:host",
                 "Content-Type" => "application/json",
                 Content => qq| { "name": "$fqdn", "ipv4addrs": [{"ipv4addr": "$ipaddr"}] } |);
    } else {
      $ua->post( "$WAPI/record:host",
                 "Content-Type" => "application/json",
                 Content => qq| { "name": "$fqdn", "ipv6addrs": [{"ipv6addr": "$ipaddr"}] } |);
    }
    print "Created new Host '$fqdn'\n";
  }
}
