File Coverage

blib/lib/Bio/CIPRES.pm
Criterion Covered Total %
statement 27 29 93.1
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 37 39 94.8


line stmt bran cond sub pod time code
1             package Bio::CIPRES;
2              
3 1     1   12973 use 5.012;
  1         3  
4 1     1   5 use strict;
  1         1  
  1         16  
5 1     1   4 use warnings;
  1         4  
  1         22  
6              
7 1     1   4 use Carp;
  1         2  
  1         41  
8 1     1   350 use Config::Tiny;
  1         705  
  1         26  
9 1     1   6 use List::Util qw/first/;
  1         2  
  1         70  
10 1     1   397 use LWP;
  1         33844  
  1         32  
11 1     1   8 use URI;
  1         3  
  1         19  
12 1     1   4 use URI::Escape;
  1         2  
  1         51  
13 1     1   175 use XML::LibXML;
  0            
  0            
14              
15             use Bio::CIPRES::Job;
16             use Bio::CIPRES::Error;
17              
18             our $VERSION = '0.003003';
19             our $SERVER = 'cipresrest.sdsc.edu';
20             our $API = 'cipresrest/v1';
21             our $DOMAIN = 'Cipres Authentication';
22              
23             my %required = ( # must be defined after config parsing
24             url => "https://$SERVER/$API/",
25             timeout => 60,
26             app_id => 'cipres_perl-E9B8D52FA2A54472BF13F25E4CD957D4',
27             user => undef,
28             pass => undef,
29             );
30              
31             my %umb_only = ( # only for UMBRELLA auth
32             eu => undef, # if this is defined, then the rest are:
33             eu_email => undef, # REQUIRED
34             app_name => undef, # REQUIRED
35             eu_institution => undef, # optional
36             eu_country => undef, # optional
37             );
38              
39             my @eu_headers = qw/
40             eu
41             eu_email
42             eu_institution
43             eu_country
44             /;
45              
46             sub new {
47              
48             my ($class, %args) = @_;
49             my $self = bless {}, $class;
50              
51             # parse properties from file or constructor
52             $self->_parse_args(%args);
53              
54             # setup user agent
55             $self->{agent} = LWP::UserAgent->new(
56             agent => __PACKAGE__ . "/$VERSION",
57             ssl_opts => {verify_hostname => 0},
58             timeout => $self->{cfg}->{timeout},
59             );
60            
61             # create URI object for easier protocol/port parsing
62             $self->{uri} = URI->new( $self->{cfg}->{url} );
63              
64             my $netloc = join ':', $self->{uri}->host, $self->{uri}->port;
65             $self->{agent}->credentials(
66             $netloc,
67             $DOMAIN,
68             $self->{cfg}->{user},
69             $self->{cfg}->{pass}
70             );
71              
72             my %headers = ( 'cipres-appkey' => $self->{cfg}->{app_id} );
73              
74             $self->{account} = $self->{cfg}->{user};
75              
76             # UMBRELLA headers
77             if (defined $self->{cfg}->{eu}) {
78             croak "eu_email required for UMBRELLA authentication"
79             if (! defined $self->{cfg}->{'eu_email'});
80             croak "app_name required for UMBRELLA authentication"
81             if (! defined $self->{cfg}->{'app_name'});
82             for my $h (@eu_headers) {
83             my $val = $self->{cfg}->{$h} // next;
84             $h =~ s/_/\-/g;
85             $headers{"cipres-$h"} = $val;
86             }
87              
88             $self->{account}
89             = uri_escape( "$self->{cfg}->{app_name}.$self->{cfg}->{eu}" );
90             }
91              
92             $self->{uri}->path("/$API/job/$self->{account}");
93             $self->{agent}->default_header(%headers);
94              
95             croak "Failed CIPRES API connection test\n"
96             if (! $self->_check_connection);
97              
98             return $self;
99              
100             }
101              
102             sub _parse_args {
103              
104             my ($self, %args) = @_;
105             my ($fn_cfg) = delete $args{conf};
106              
107             # set defaults
108             $self->{cfg} = {%required}; # copy, don't reference!
109              
110             # read from config file if asked, overwriting defaults
111             if (defined $fn_cfg) {
112             croak "Invalid or missing configuration file specified"
113             if (! -e $fn_cfg);
114             my $cfg = Config::Tiny->read( $fn_cfg )
115             or croak "Error reading configuration file: $@";
116             $self->{cfg}->{$_} = $cfg->{_}->{$_}
117             for (keys %{ $cfg->{_} });
118              
119             }
120              
121             # read parameters from constructor, overwriting if present
122             $self->{cfg}->{$_} = $args{$_} for (keys %args);
123              
124             # check that all defined fields are valid
125             my @extra = grep {! exists $required{$_} && ! exists $umb_only{$_}}
126             keys %{ $self->{cfg} };
127             croak "Unexpected config variables found (@extra) -- check syntax"
128             if (scalar @extra);
129              
130             # check that all required fields are defined
131             my @missing = grep {! defined $self->{cfg}->{$_}} keys %required;
132             croak "Required config variables missing (@missing) -- check syntax"
133             if (scalar @missing);
134              
135             # TODO: further parameter validation ???
136            
137             # Do necessary url-encoding
138             for (qw/user pass/) {
139             $self->{cfg}->{$_} = uri_escape( $self->{cfg}->{$_} );
140             }
141              
142             return 1;
143              
144             }
145              
146             sub list_jobs {
147              
148             my ($self) = @_;
149              
150             my $res = $self->_get( "$self->{uri}?expand=true" );
151             my $dom = XML::LibXML->load_xml('string' => $res);
152              
153             return map {
154             Bio::CIPRES::Job->new( agent => $self->{agent}, dom => $_ )
155             } $dom->findnodes('/joblist/jobs/jobstatus');
156              
157             }
158              
159             sub get_job {
160              
161             my ($self, $handle) = @_;
162              
163             my $res = $self->_get( "$self->{uri}/$handle" );
164             my $dom = XML::LibXML->load_xml('string' => $res);
165              
166             return Bio::CIPRES::Job->new(
167             agent => $self->{agent},
168             dom => $dom,
169             );
170              
171             }
172              
173             sub submit_job {
174              
175             my ($self, @args) = @_;
176              
177             my $res = $self->_post( $self->{uri}, @args );
178             my $dom = XML::LibXML->load_xml('string' => $res);
179              
180             return Bio::CIPRES::Job->new(
181             agent => $self->{agent},
182             dom => $dom,
183             );
184              
185             }
186              
187             sub _get {
188              
189             my ($self, $url) = @_;
190              
191             my $res = $self->{agent}->get( $url )
192             or croak "Error fetching file from $url: $@";
193              
194             die Bio::CIPRES::Error->new( $res->content )
195             if (! $res->is_success);
196              
197             return $res->content;
198              
199             }
200              
201             sub _post {
202              
203             my ($self, $url, @args) = @_;
204              
205             my $res = $self->{agent}->post(
206             $url,
207             [ @args ],
208             'content_type' => 'form-data',
209             ) or croak "Error POSTing to $url: $@";
210              
211             die Bio::CIPRES::Error->new( $res->content )
212             if (! $res->is_success);
213              
214             return $res->content;
215              
216             }
217              
218             sub _check_connection {
219              
220             # do a basic check of the API, fetching the link page
221              
222             my ($self) = @_;
223              
224             my $uri = $self->{uri}->clone();
225             $uri->path($API);
226             my $res = $self->_get( "$uri" );
227             my $dom = XML::LibXML->load_xml('string' => $res);
228              
229             $dom = $dom->firstChild;
230             return( $dom->nodeName eq 'links' );
231              
232              
233             }
234              
235             1;
236              
237              
238             __END__