<commit_before>package VMware::API::vCloud;

use Data::Dumper;
use LWP;
use XML::Simple;
use strict;

our $VERSION = 'VERSIONTAG';

# ADMIN OPTS - http://www.vmware.com/support/vcd/doc/rest-api-doc-1.5-html/landing-admin_operations.html
# USER OPTS - http://www.vmware.com/support/vcd/doc/rest-api-doc-1.5-html/landing-user_operations.html

=head1 NAME

VMware::API::vCloud - The VMware vCloud API

=head1 SYNOPSIS


  my $api = new VMware::API::vCloud (
    $hostname, $username, $password, $orgname
  );
  
  my $raw_login_data = $vcd->login;

=head1 DESCRIPTION

This module provides a bare interface to VMware's vCloud REST API.

VMware::vCloud is designed for high level usage with vCloud Director. This 
module, however, provides a more low-level access to the REST interface.

Responses received from vCloud are in XML. They are translated via XML::Simple
with ForceArray set for consistency in nesting. This is the object returned.

Aside from the translation of XML into a perl data structure, no further 
alteration is performed on the data.

HTTP errors are automatically parsed and die() is called. If you need to perform
a dangerous action, do so in an eval block and evaluate $@.

=head1 OBJECT METHODS

These methods are not API calls. They represent the methods that create
this module as a "wrapper" for the vCloud API.

=head2 new()

This method creates the vCloud object.

Arguments

=over 4

=item * hostname

=item * username

=item * password

=item * organization

=back

=cut

sub new {
  my $class = shift @_;
  my $self  = {};

  $self->{hostname} = shift @_;
  $self->{username} = shift @_;
  $self->{password} = shift @_;
  $self->{orgname}  = shift @_;

  $self->{debug}        = 0; # Defaults to no debug info
  $self->{die_on_fault} = 1; # Defaults to dieing on an error
  $self->{ssl_timeout}  = 3600; # Defaults to 1h

  $self->{orgname} = 'System' unless $self->{orgname};

  $self->{conf} = shift @_ if defined $_[0] and ref $_[0];
  $self->{debug} = $self->{conf}->{debug} if defined $self->{conf}->{debug};

  bless($self,$class);

  $self->_regenerate();
  
  $self->_debug("Loaded VMware::vCloud v" . our $VERSION . "\n") if $self->{debug};
  return $self;
}

=head2 config()

  $vcd->config( debug => 1 );

=over 4

=item debug

  0 for no debugging.
  1 to turn on basic debug messages. 
  2 to display learned variables on exit.
  3 to show all XML transactions.

=item die_on_fault - 1 to cause the program to die verbosely on a soap fault. 0 for the fault object to be returned on the call and for die() to not be called. Defaults to 1. If you choose not to die_on_fault (for example, if you are writing a CGI) you will want to check all return objects to see if they are fault objects or not.

=item ssl_timeout - seconds to wait for timeout. Defaults to 3600. (1hr) This is how long a transaction response will be waited for once submitted. For slow storage systems and full clones, you may want to up this higher. If you find yourself setting this to more than 6 hours, your vCloud setup is probably not in the best shape.

=item hostname, orgname, username and password - All of these values can be changed from the original settings on new(). This is handing for performing multiple transactions across organizations.

=back

=cut

sub config {
  my $self = shift @_;

  my %input = @_;
  my @config_vals = qw/debug die_on_fault hostname orgname password ssl_timeout username/;
  my %config_vals = map { $_,1; } @config_vals;

  for my $key ( keys %input ) {
    if ( $config_vals{$key} ) {
      $self->{$key} = $input{$key};
    } else {
      warn 'Config key "$key" is being ignored. Only the following options may be configured: '
         . join(", ", @config_vals ) . "\n";
    }
  }

  $self->_regenerate();

  my %out;
  map { $out{$_} = $self->{$_} } @config_vals;

  return wantarray ? %out : \%out;
}

### Internal methods

# $self->{raw}->{version} - Full data on the API version from login (populated on api_version() call)
# $self->{raw}->{login}
# $self->{learned}->{version} - API version number (populated on api_version() call)
# $self->{learned}->{url}->{login} - Authentication URL (populated on api_version() call)
# $self->{learned}->{url}->{orglist}

sub DESTROY {
  my $self = shift @_;
  my @dump = split "\n", Dumper($self->{learned});
  pop @dump; shift @dump;
  $self->_debug_with_level(2,"Learned variables: \n" . join("\n",@dump));
}

sub _debug {
  my $self  = shift @_;
  return undef unless $self->{debug};
  while ( my $debug = shift @_ ) {
    chomp $debug;
    print STDERR "DEBUG: $debug\n";
  }
}

sub _debug_with_level {
  my $self  = shift @_;
  my $value = shift @_;
  return undef if $self->{debug} < $value;
  $self->_debug(@_);
}

sub _fault {
  my $self = shift @_;
  my @error = @_;
  
  my $message = "\nERROR: ";
  
  if ( length(@error) and ref $error[0] eq 'HTTP::Response' ) {
    $message .= $error[0]->status_line;
    if ( $error[0]->content ) {
      $self->_debug(Dumper(\@error));
      my $ret = $self->_parse_xml($error[0]->content);
      $message .= ' : '. $ret->{message};
    }
    die $message;
  }
  
  while ( my $error = shift @error ) {
    if ( ref $error eq 'SCALAR' ) {
      chomp $error;
      $message .= $error;
    } else {
      $message .= Dumper($error);
    }
  }
}

sub _regenerate {
  my $self = shift @_;  
  $self->{ua} = LWP::UserAgent->new;<commit_msg>Better handling of server connection timeout.<commit_after>package VMware::API::vCloud;

use Data::Dumper;
use LWP;
use XML::Simple;
use strict;

our $VERSION = 'VERSIONTAG';

# ADMIN OPTS - http://www.vmware.com/support/vcd/doc/rest-api-doc-1.5-html/landing-admin_operations.html
# USER OPTS - http://www.vmware.com/support/vcd/doc/rest-api-doc-1.5-html/landing-user_operations.html

=head1 NAME

VMware::API::vCloud - The VMware vCloud API

=head1 SYNOPSIS


  my $api = new VMware::API::vCloud (
    $hostname, $username, $password, $orgname
  );
  
  my $raw_login_data = $vcd->login;

=head1 DESCRIPTION

This module provides a bare interface to VMware's vCloud REST API.

VMware::vCloud is designed for high level usage with vCloud Director. This 
module, however, provides a more low-level access to the REST interface.

Responses received from vCloud are in XML. They are translated via XML::Simple
with ForceArray set for consistency in nesting. This is the object returned.

Aside from the translation of XML into a perl data structure, no further 
alteration is performed on the data.

HTTP errors are automatically parsed and die() is called. If you need to perform
a dangerous action, do so in an eval block and evaluate $@.

=head1 OBJECT METHODS

These methods are not API calls. They represent the methods that create
this module as a "wrapper" for the vCloud API.

=head2 new()

This method creates the vCloud object.

Arguments

=over 4

=item * hostname

=item * username

=item * password

=item * organization

=back

=cut

sub new {
  my $class = shift @_;
  my $self  = {};

  $self->{hostname} = shift @_;
  $self->{username} = shift @_;
  $self->{password} = shift @_;
  $self->{orgname}  = shift @_;

  $self->{debug}        = 0; # Defaults to no debug info
  $self->{die_on_fault} = 1; # Defaults to dieing on an error
  $self->{ssl_timeout}  = 3600; # Defaults to 1h

  $self->{orgname} = 'System' unless $self->{orgname};

  $self->{conf} = shift @_ if defined $_[0] and ref $_[0];
  $self->{debug} = $self->{conf}->{debug} if defined $self->{conf}->{debug};

  bless($self,$class);

  $self->_regenerate();
  
  $self->_debug("Loaded VMware::vCloud v" . our $VERSION . "\n") if $self->{debug};
  return $self;
}

=head2 config()

  $vcd->config( debug => 1 );

=over 4

=item debug

  0 for no debugging.
  1 to turn on basic debug messages. 
  2 to display learned variables on exit.
  3 to show all XML transactions.

=item die_on_fault - 1 to cause the program to die verbosely on a soap fault. 0 for the fault object to be returned on the call and for die() to not be called. Defaults to 1. If you choose not to die_on_fault (for example, if you are writing a CGI) you will want to check all return objects to see if they are fault objects or not.

=item ssl_timeout - seconds to wait for timeout. Defaults to 3600. (1hr) This is how long a transaction response will be waited for once submitted. For slow storage systems and full clones, you may want to up this higher. If you find yourself setting this to more than 6 hours, your vCloud setup is probably not in the best shape.

=item hostname, orgname, username and password - All of these values can be changed from the original settings on new(). This is handing for performing multiple transactions across organizations.

=back

=cut

sub config {
  my $self = shift @_;

  my %input = @_;
  my @config_vals = qw/debug die_on_fault hostname orgname password ssl_timeout username/;
  my %config_vals = map { $_,1; } @config_vals;

  for my $key ( keys %input ) {
    if ( $config_vals{$key} ) {
      $self->{$key} = $input{$key};
    } else {
      warn 'Config key "$key" is being ignored. Only the following options may be configured: '
         . join(", ", @config_vals ) . "\n";
    }
  }

  $self->_regenerate();

  my %out;
  map { $out{$_} = $self->{$_} } @config_vals;

  return wantarray ? %out : \%out;
}

### Internal methods

# $self->{raw}->{version} - Full data on the API version from login (populated on api_version() call)
# $self->{raw}->{login}
# $self->{learned}->{version} - API version number (populated on api_version() call)
# $self->{learned}->{url}->{login} - Authentication URL (populated on api_version() call)
# $self->{learned}->{url}->{orglist}

sub DESTROY {
  my $self = shift @_;
  my @dump = split "\n", Dumper($self->{learned});
  pop @dump; shift @dump;
  $self->_debug_with_level(2,"Learned variables: \n" . join("\n",@dump));
}

sub _debug {
  my $self  = shift @_;
  return undef unless $self->{debug};
  while ( my $debug = shift @_ ) {
    chomp $debug;
    print STDERR "DEBUG: $debug\n";
  }
}

sub _debug_with_level {
  my $self  = shift @_;
  my $value = shift @_;
  return undef if $self->{debug} < $value;
  $self->_debug(@_);
}

sub _fault {
  my $self = shift @_;
  my @error = @_;
  
  my $message = "\nERROR: ";
  
  if ( length(@error) and ref $error[0] eq 'HTTP::Response' ) {
    if ( $error[0]->content ) {
      $self->_debug(Dumper(\@error));
      $self->_debug('ERROR Status Line: ' . $error[0]->status_line);
      $self->_debug('ERROR Content: ' . $error[0]->content);

      my $ret; # Try parsing as XML, or fallback to content as message
      eval { $ret = $self->_parse_xml($error[0]->content); };
      $message .= ( $@ ? $error[0]->content : $error[0]->status_line . ' : '. $ret->{message} );
    }
    die $message;
  }
  
  while ( my $error = shift @error ) {
    if ( ref $error eq 'SCALAR' ) {
      chomp $error;
      $message .= $error;
    } else {
      $message .= Dumper($error);
    }
  }
}

sub _regenerate {
  my $self = shift @_;  
  $self->{ua} = LWP::UserAgent->new;
{'fraction_non_alphanumeric': 0.13850616388687453, 'fraction_numerical': 0.007704858593183466, 'mean_word_length': 3.388623707239459, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 106, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '628379', 'n_tokens_mistral': 3786, 'n_tokens_neox': 3668, 'n_words': 1291}