Thursday, December 8, 2016

Invoke workflow with Perl and REST

Ever heard of REST ?  In a nutshell : REST is a way of talking to a remote 'system' using existing standards.  http(s) as the protocol and json or xml as the carrier.  Reading is a GET, Writing is a POST.

Oh, and did you know WFA has a REST api ?  That's right, you can talk to WFA with about any programming language there is.

I'm a powershell / .NET guy, so I'll post a powershell sample ASAP.  But I just saw this nice piece of PERL code from my colleague Todd and couldn't wait sharing.  I guess Todd won't mind :), and hopefully he'll add some more comment to this article.

As far as I can read the perl code, he has built in the capture of CLI parameters, he's validating them and basing the workflow-userinput and workflow-name on the CLI input.  Obviously you don't just want to copy-paste this, you would need change the logic of the CLI input to your needs, for after all, a nice piece of code, that allows you to study how the workflow config can be passed by cli-input.

#!/usr/bin/perl

# If we primarily run the workflow from outside WFA, then we should
# use no-op Unix scripts that return 'false' so that the workflow
# fails.

use strict;
use MIME::Base64;
use REST::Client;
use XML::Simple;
#use XML::SAX::Expat; # This is just here for pp to work right when packaging.
use Getopt::Long;

use Data::Dumper;

$ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;

my $WFA_SERVER          = '';
my $WFA_USER            = 'admin';
my $WFA_USER_PW         = '';
my $EOD_WF_NAME_cDOT    = 'Cdot workflowname';
my $EOD_WF_NAME_7MODE   = '7Mode workflowname';


my $WF_EXEC_TIMEOUT_MINS = 30;

my $TRUE = 1;
my $FALSE = 0;

my $global_error; # global error variable

my $workflow_name;

my $cfg = XMLin(undef,ForceArray => ['ENV'], KeyAttr => [])
   or die "Can't open cfg file: $!";
die $global_error unless cfg_is_valid($cfg);

my %cli_params;
#$cli_params{'environment'} = 'SUP';
$cli_params{'timeout'} = $WF_EXEC_TIMEOUT_MINS;
$cli_params{'na_mode'} = 'cDOT';

GetOptions(
   'environment=s'  => \$cli_params{'environment'},
   'na_mode=s'      => \$cli_params{'na_mode'},
   'no-op'         => \$cli_params{'no-op'},
   'timeout=i'      => \$cli_params{'timeout'},
   'clone-set=s'    => \$cli_params{'clone-set'},
   'help'           => \$cli_params{'help'},
);
usage($cfg) if $cli_params{'help'};
validate_params(\%cli_params, $cfg);

#$workflow_name = $cfg->{'WF_cDOT'}{'name'};

if ( $cli_params{'no-op'} ) {
   $workflow_name = $cfg->{'WF_NOOP'}{'name'};
}
elsif ( $cli_params{'na_mode'} =~ m/cDOT/i ) {
  $workflow_name = $cfg->{'WF_cDOT'}{'name'};
}
elsif( $cli_params{'na_mode'} =~ m/7Mode/i ){
  $workflow_name = $cfg->{'WF_7MODE'}{'name'};
}

my $headers = {
        Authorization => 'Basic '
          . encode_base64($cfg->{'WFA_SERVER'}{'user'}
          . ':'
          . $cfg->{'WFA_SERVER'}{'password'}),
        'Content-type' => 'application/xml',
};

my $wfa_handle = REST::Client->new( {host => "https://" . $cfg->{'WFA_SERVER'}{'host'} ."/rest",} );

my $workflow = get_workflow_xml($wfa_handle, $headers, $workflow_name);
if ( ! $workflow ) {
   print STDERR "ERROR: unable to update storage for " . $cli_params{'environment'} . "\n";
   print STDERR "WFA server returned response code: " . $wfa_handle->responseCode() . "\n";
   print STDERR $wfa_handle->responseContent() . "\n";
   exit (1);
}

my $uuid = $workflow->{'workflow'}{'uuid'};

my %wf_inputs = (
   'environment_type'    => $cli_params{'environment'},
   'AutoSelectSnap'      => 'true',
   'called_from_rest'    => 'true',
   'CloneSetName'       => $cli_params{'clone-set'},
);

my $job_id = run_workflow($wfa_handle, $headers, $uuid, \%wf_inputs);
if ( $job_id ) {
  if ( get_workflow_status( $wfa_handle, $job_id, $headers ) ){
    exit(0);
  }
  else{
    print STDERR $global_error;
    exit(1);
  }
}
else{
  print STDERR "ERROR: Unable to update storage for " . $cli_params{'environment'}  . "\n";
  print STDERR "WFA server returned response code: "  . $wfa_handle->responseCode() . "\n";
  print STDERR $wfa_handle->responseContent()                                       . "\n";
  exit(1);
}
exit;

############################## FUNCTIONS #################################

sub get_workflow_xml {
 my ($wfa_handle,$headers,$workflow) = (@_);
 my ($wfa,$uuid);

 $workflow =~ s/\s/%20/g;
 $wfa_handle->GET("/workflows?name=${workflow}", $headers);

 if ($wfa_handle->responseCode() > 201) {
  my $error = $wfa_handle->responseContent();
         print "Error connecting to WFA: $error\n";
         return undef;
 } 
 $wfa  = XMLin($wfa_handle->responseContent(), ForceArray => ['userInput']);

 return $wfa;
}

sub run_workflow {
 my ($wfa_handle,$headers,$uuid,$inputs) = (@_);
 my ($postdata,$key,$value,$sleep,$date);

 # Setup workflow input in XML format.
 $postdata = qq{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workflowInput>
  <userInputValues>};

 # Loop through our user inputs and add them in the XML.
 for $key (keys %{$inputs}) {
  $postdata .= "\n";
  $value = $inputs->{$key};
  $postdata .= qq{    <userInputEntry key="$key" value="$value"/>};
 } 

 # Finish the XML.
 $postdata .= qq{
  </userInputValues>
</workflowInput>
}; 

 # Submit the workflow.
 $wfa_handle->POST( "/workflows/$uuid/jobs" , $postdata , $headers);

 # Get results of workflow submission, sleeping between each time we check,
 # until it is finished.
 if ($wfa_handle->responseCode() > 201) {
  $date = localtime;
      return undef;
 }
   else {
  my $result = XMLin($wfa_handle->responseContent());
  my $jobId  = $result->{'jobId'};
  $date = localtime;
  print "$date: JobId $jobId submitted.\n";
      return $jobId;
 }
}

sub get_workflow_status{
   my ( $wfa_handle, $jobId, $headers ) = @_;
   
   my $sleep = 1;
   my $date;
   
   my $wf_start_time = time();
   
   my $jobfinished = $FALSE;
   my $timeout     = $FALSE;
   my $fail        = $FALSE;
   while (! $jobfinished && ! $timeout ) {
      $date = localtime;
      $wfa_handle->GET("/workflows/${uuid}/jobs/$jobId", $headers);
      my $jobs      = XMLin($wfa_handle->responseContent());
      my $jobStatus = $jobs->{'jobStatus'}{'jobStatus'};
      my $startTime = $jobs->{'jobStatus'}{'startTime'};
      my $endTime   = $jobs->{'jobStatus'}{'endTime'};
      my $error     = $jobs->{'jobStatus'}{'errorMessage'} if ($jobs->{'jobStatus'}{'errorMessage'});
      if ($jobStatus =~ /completed/i) {
         print "$date: Job $jobId finished successfully.  Started at $startTime and ended at $endTime.\n";
         $jobfinished++;
      } elsif ($jobStatus =~ /failed/i) {
         #print "$date: Job $jobId failed.  Started at $startTime and ended at $endTime.  Error message was: $error\n";
         $fail = $TRUE;
         $global_error = $error;
         $jobfinished++;
      } else {
         #print "$date: Job $jobId still running.  Status is $jobStatus.\n";
         sleep $sleep;
      }      
      $timeout = (time() - $wf_start_time) > $cli_params{'timeout'}*60;
   }

  if ( ! $fail ) {
    return 1;
  }
  elsif ( $timeout ){
    $global_error = 'Workflow timed out';
    return undef;
  }
  else{
    return undef;
  }
}


sub validate_params{
  my ($params_r, $cfg_r ) = @_;
  
  my $valid_envs;
  my @valid_envs;
  foreach my $env ( @{$cfg_r->{'ENVS'}{'ENV'}} ){
    push(@valid_envs, $env->{'name'});
  }
  $valid_envs = join('|', @valid_envs);
  
  $params_r->{'environment'} =~ tr/a-z/A-Z/;
  
  usage($cfg_r) unless ($params_r->{'environment'}
                  && ($params_r->{'environment'} =~ m/${valid_envs}/)
                  && $cli_params{'clone-set'}
                 );

  
  if ( $params_r->{'na_mode'} ) {
   usage($cfg_r) unless ($params_r->{'na_mode'} =~ m/cDOT|7Mode/i );
  }
}

sub usage{
  my ( $cfg_r ) = @_;
  
  my $valid_envs;
  my @valid_envs;
  foreach my $env ( @{$cfg_r->{'ENVS'}{'ENV'}} ){
    push(@valid_envs, $env->{'name'});
  }
  $valid_envs = join('|', @valid_envs);
  
    print <<EOF;
the-name-of-this-script.pl --environment=<${valid_envs}>
EOF

  exit(0);
}

sub cfg_is_valid{
  my ( $cfg_r ) = @_; 

  if ( ! (      exists $cfg_r->{'WFA_SERVER'}
             && exists $cfg_r->{'WF_cDOT'}
             && exists $cfg_r->{'WF_7MODE'}
             && exists $cfg_r->{'NA_MODE'}
             && exists $cfg_r->{'ENVS'}
             ) ){
    $global_error =  "Invalid config file, does not contain required entries";
    return $FALSE;
  }
  elsif( ! ( exists $cfg_r->{'WFA_SERVER'}{'host'}
            && exists $cfg_r->{'WFA_SERVER'}{'user'}
            && exists $cfg_r->{'WFA_SERVER'}{'password'})
        ){
    $global_error =  "Invalid config file: WFA_SERVER must contain host, user, & password attributes.";
    return $FALSE;
  }
  elsif( ! exists $cfg_r->{'WF_cDOT'}{'name'} ){
    $global_error = "Invalid config file: WF_cDOT must contain name entry";
    return $FALSE;
  }
  elsif( ! exists $cfg_r->{'WF_7MODE'}{'name'} ){
    $global_error =  "Invalid config file: WF_7MODE must contain name entry";
    return $FALSE;
  }
  elsif( ! exists $cfg_r->{'NA_MODE'}{'mode'} ){
    $global_error = "Invalid config file: NA_MODE or mode name not present";
    return $FALSE;
  }
  
  if (! exists $cfg_r->{'WFA_SERVER'}{'timeout_mins'} ) {
   $cfg_r->{'WFA_SERVER'}{'timeout_mins'} = $WF_EXEC_TIMEOUT_MINS;
  }
  
  foreach my $env ( @{$cfg_r->{'ENVS'}{'ENV'}} ){
    $env->{'name'} =~ tr/a-z/A-Z/;    
  }
  
  return $TRUE;
  
}

No comments :

Post a Comment