File Coverage

blib/lib/Acme/Globus.pm
Criterion Covered Total %
statement 15 147 10.2
branch 0 42 0.0
condition 0 8 0.0
subroutine 5 45 11.1
pod 37 39 94.8
total 57 281 20.2


line stmt bran cond sub pod time code
1             package Acme::Globus;
2              
3             # ABSTRACT: Interface to the Globus research data sharing service
4              
5 1     1   463 use strict;
  1         2  
  1         25  
6 1     1   5 use warnings;
  1         1  
  1         19  
7              
8 1     1   4 use Carp ;
  1         2  
  1         47  
9 1     1   540 use JSON ;
  1         9274  
  1         7  
10 1     1   1096 use Net::OpenSSH ;
  1         25587  
  1         1656  
11              
12             =pod
13              
14             =head1 NAME
15              
16             Globus - Object-Oriented interface to Globus
17              
18             =head1 DESCRIPTION
19              
20             Globus is a tool that allows the sharing of scientific data between
21             researchers and institutions. Globus enables you to transfer your
22             data using just a web browser, or using their SSH interface at
23             cli.globusonline.org.
24              
25             This is a client library for the Globus CLI.
26              
27             For detailed documentation of the API,
28             see L.
29              
30             =head1 CAVEATS
31              
32             This code is a work in progress, focusing on my needs at the moment
33             rather than covering all the capabilities of the Globus CLI. It is
34             therefore very stubtastic.
35              
36             This module also relies very much on SSH, and thus the rules of
37             private and public keys. Therefore, using it as a shared tool would
38             be ill-advised if not impossible.
39              
40             =head1 SYNOPSIS
41              
42             my $g = Globus->new($username,$path_to_ssh_key) ;
43             $g->endpoint_add_shared( 'institution#endpoint', $directory, $endpoint_name ) ;
44             $g->acl_add( $endpoint . '/', 'djacoby@example.com' ) ;
45            
46             =head1 METHODS
47              
48             =head2 BASICS
49              
50             =head3 B
51              
52             Creates a new Globus object. Takes two options:
53             the username and path to the SSH key you use to connect to Globus.
54              
55             =head3 B
56              
57             =head3 B
58              
59             =head3 B
60              
61             =head3 B
62              
63             These commands return and change the username and keypath you use to
64             connect to Globus.
65              
66             =cut
67              
68             sub new {
69 0     0 1   my ( $class, $username, $key_path ) = @_ ;
70 0           my $self = {} ;
71 0           bless $self, $class ;
72 0   0       $self->{username} = $username || 'none' ;
73 0   0       $self->{key_path} = $key_path || 'none' ;
74 0           return $self ;
75             }
76              
77             sub set_username {
78 0     0 1   my ( $self, $username ) = @_ ;
79 0           $self->{username} = $username ;
80             }
81              
82             sub set_key_path {
83 0     0 1   my ( $self, $key_path ) = @_ ;
84 0           $self->{key_path} = $key_path ;
85             }
86              
87             sub get_username {
88 0     0 1   my ($self) = @_ ;
89 0   0       return $self->{username} || 'NO USER' ;
90             }
91              
92             sub get_key_path {
93 0     0 1   my ($self) = @_ ;
94 0   0       return $self->{key_path} || 'NO KEY PATH' ;
95             }
96              
97             =head2 TASK MANAGEMENT
98              
99             =head3 B
100              
101             =head3 B
102              
103             =head3 B
104              
105             =head3 B
106              
107             =head3 B
108              
109             =head3 B
110              
111             We do not do much with task management, so these are currently stubs.
112              
113             =cut
114              
115       0 1   sub cancel { }
116       0 1   sub details { }
117       0 1   sub events { }
118       0 1   sub modify { }
119       0 1   sub status { }
120       0 1   sub wait { }
121              
122             =head2 TASK CREATION
123              
124             =head3 B
125              
126             =head3 B
127              
128             Currently stubs
129              
130             =head3 B
131              
132             =head3 B
133              
134             Both commands take a source, or from path (including endpoint),
135             a destination, or to path (includint endpoint), and a boolean indicating
136             whether you're copying recursively or not.
137              
138             =cut
139              
140       0 1   sub delete { }
141       0 1   sub rm { }
142              
143             sub scp {
144 0     0 1   my ( $self, $from_path, $to_path, $recurse ) = @_ ;
145 0 0         $recurse = $recurse ? '-r' : '' ;
146 0           my $command = qq{scp $recurse $from_path $to_path} ;
147             my $result
148 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
149 0           return $result ;
150             }
151              
152             sub transfer {
153 0     0 1   my ( $self, $from_path, $to_path, $recurse ) = @_ ;
154 0 0         $recurse = $recurse ? '-r' : '' ;
155 0           my $command = qq{transfer $from_path $to_path} ;
156             my $result
157 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
158 0           return $result ;
159             }
160              
161             =head2 FILE MANAGEMENT
162              
163             =head3 B
164              
165             Works?
166              
167             =head3 B
168              
169             =head3 B
170              
171             Stubs
172              
173             =cut
174              
175             sub ls {
176 0     0 1   my ( $self, $file_path ) = @_ ;
177 0           my $command = qq{ls $file_path} ;
178             my $result
179 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
180 0           my @result = split m{\r?\n}, $result ;
181 0 0         return wantarray ? @result : \@result ;
182             }
183              
184       0 1   sub mkdir { }
185       0 1   sub rename { }
186              
187             =head2 ENDPOINT MANAGEMENT
188              
189             =head3 B
190              
191             =head3 B
192              
193             =head3 B
194              
195             acl-* is the way that Globus refers to permissions
196              
197             By the interface, Globus supports adding shares by email address,
198             by Globus username or by Globus group name. This module sticks to
199             using email address. acl_add() takes an endpoint, an email address
200             you're sharing to, and a boolean indicating whether this share is
201             read-only or read-write. acl_add() returns a share id.
202              
203             acl_remove() uses that share id to identify which shares are to be
204             removed.
205              
206             acl_list() returns an array of hashes containing the information about
207             each user with access to an endpoint, including the share ID and permissions.
208              
209             =cut
210              
211             sub identity_details {
212 0     0 0   my ( $self, $identity_id ) = @_ ;
213 0           my $command = qq{identity-details $identity_id } ;
214             my $result
215 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
216 0 0         return {} unless $result =~ m{\w} ;
217 0           my $obj = decode_json $result ;
218 0 0         return wantarray ? %$obj : $obj ;
219             }
220              
221             sub acl_add {
222 0     0 1   my ( $self, $endpoint, $email, $rw ) = @_ ;
223 0           my $readwrite = 'rw' ;
224 0 0         $readwrite = 'r' unless $rw ;
225 0           my $command
226             = qq{acl-add $endpoint --identityusername=${email} --perm $readwrite }
227             ;
228             my $result
229 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
230 0           my ($id) = reverse grep {m{\w}} split m{\s}, $result ;
  0            
231 0           return $id ;
232             }
233              
234             sub acl_list {
235 0     0 1   my ( $self, $endpoint ) = @_ ;
236 0           my $command = qq{acl-list $endpoint} ;
237             my $result
238 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
239 0           my $slist = decode_json $result ;
240 0           my @list = grep { $_->{permissions} ne 'rw' } @$slist ;
  0            
241 0 0         return wantarray ? @list : \@list ;
242             }
243              
244             sub acl_remove {
245 0     0 1   my ( $self, $endpoint_uuid, $share_uuid ) = @_ ;
246 0           my $command = qq{acl-remove $endpoint_uuid --id $share_uuid} ;
247             my $result
248 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
249 0           return $result ;
250             }
251              
252             =head3 B
253              
254             =head3 B
255              
256             =head3 B
257              
258             =head3 B
259              
260             endpoint_add_shared() handles the specific case of creating an endpoint
261             from an existing endpoint, not the general case. It takes the endpoint
262             where you're sharing from, the path you're sharing, and the endpoint
263             you're creating. If you are user 'user' and creating the endpoint 'test',
264             the command takes 'test', not 'user#test'.
265              
266             endpoint_remove and endpoint_list, however, take a full endpoint name, like 'user#test'.
267              
268             Current usage is endpoint_list for a list of all our shares, and endpoint_search
269             for details of each individual share
270              
271             =head3 B
272              
273             =head3 B
274              
275             list_my_endpoints() and search_my_endpoints() were added once I discovered
276             the failings of existing list and search. These tools return a hashref
277             of hashrefs holding the owner, host_endpoint, host_endpoint_name,
278             credential_status, and most importantly, the id, legacy_name and display_name.
279              
280             For older shares, legacy_name will be something like 'purduegcore#hr00001_firstshare'
281             and display_name will be 'n/a', while for newer shares, legacy_name will be
282             'purduegcore#SAME_AS_ID' and display_name will be like older shares' legacy_name,
283             'purduegcore#hr99999_filled_the_space'. In both cases, the value you want
284             to use to get details or to remove a share is the id, which is a UUID.
285              
286             =cut
287              
288             sub endpoint_add_shared {
289 0     0 1   my ( $self, $sharer_endpoint, $path, $endpoint ) = @_ ;
290              
291             # my $command
292             # = qq{endpoint-add --sharing "$sharer_endpoint$path" $endpoint } ;
293             # my $command
294             # = qq{endpoint-add -n $endpoint --sharing "$sharer_endpoint$path" } ;
295 0           my $command = join ' ',
296             q{endpoint-add},
297             q{--sharing}, "$sharer_endpoint$path",
298             q{-n}, $endpoint,
299             ;
300             my $result
301 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
302 0           return $result ;
303             }
304              
305             # sub endpoint_list {
306             # my ( $self, $endpoint ) = @_ ;
307             # my $command ;
308             # if ($endpoint) {
309             # $command = qq{endpoint-list $endpoint } ;
310             # }
311             # else {
312             # $command = qq{endpoint-list} ;
313             # }
314             # my $result
315             # = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
316             # my @result = map { ( split m{\s}, $_ )[0] } split "\n", $result ;
317             # return wantarray ? @result : \@result ;
318             # }
319              
320             #lists all my endpoint
321             sub endpoint_list {
322 0     0 1   my ($self) = @_ ;
323 0           my $command = 'endpoint-search --scope=my-endpoints' ;
324             my $result
325 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
326 0           my @result = map { s{\s}{}g ; $_ }
  0            
327 0           map { ( reverse split m{:} )[0] }
328 0           grep {m{Legacy}}
  0            
329             split m{\n}, $result ;
330 0 0         return wantarray ? @result : \@result ;
331             }
332              
333             sub endpoint_search {
334 0     0 1   my ( $self, $search ) = @_ ;
335 0 0         return {} unless $search ;
336 0           my $command = qq{endpoint-search $search --scope=my-endpoints} ;
337             my $result
338 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
339             my %result = map {
340 0           chomp ;
  0            
341 0           my ( $k, $v ) = split m{\s*:\s}, $_ ;
342 0           $k => $v
343             }
344             split m{\n}, $result ;
345 0 0         return wantarray ? %result : \%result ;
346             }
347              
348             sub list_my_endpoints {
349 0     0 1   my ($self) = @_ ;
350 0           my $command = 'endpoint-search --scope=my-endpoints' ;
351             my $result
352 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
353             my %result = map {
354 0           my $hash ;
  0            
355             %$hash = map {
356 0           my ( $k, $v ) = split m{\s*:\s*} ;
  0            
357 0           $k =~ s{\s+}{_}gmx ;
358 0           $k = lc $k ;
359 0           $k => $v
360             }
361             split m{\n} ;
362             my $id
363             = $hash->{display_name} ne 'n/a'
364             ? $hash->{display_name}
365 0 0         : $hash->{legacy_name} ;
366 0           $id => $hash ;
367             }
368             split m{\n\n}, $result ;
369 0 0         return wantarray ? %result : \%result ;
370             }
371              
372             sub search_my_endpoints {
373 0     0 1   my ( $self, $search ) = @_ ;
374 0           my %result ;
375 0           my $command = qq{endpoint-search $search --scope=my-endpoints} ;
376             my $result
377 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
378             %result = map {
379 0           my $hash ;
  0            
380             %$hash = map {
381 0           my ( $k, $v ) = split m{\s*:\s*} ;
  0            
382 0           $k =~ s{\s+}{_}gmx ;
383 0           $k = lc $k ;
384 0           $k => $v
385             }
386             split m{\n} ;
387             my $id
388             = $hash->{display_name} ne 'n/a'
389             ? $hash->{display_name}
390 0 0         : $hash->{legacy_name} ;
391 0           $id => $hash ;
392             }
393             split m{\n\n}, $result ;
394 0 0         return wantarray ? %result : \%result ;
395             }
396              
397             sub endpoint_remove {
398 0     0 1   my ( $self, $endpoint ) = @_ ;
399 0           my $command = qq{endpoint-remove $endpoint} ;
400             my $result
401 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
402 0           return $result ;
403             }
404              
405             # Sucks. Use endpoint_search instead
406             sub endpoint_details {
407 0     0 0   my ( $self, $endpoint ) = @_ ;
408 0           my $command = qq{endpoint-details $endpoint} ;
409             my $result
410 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
411              
412             my %result = map {
413 0           chomp ;
  0            
414 0           my ( $key, $value ) = split m{\s*:\s*}, $_ ;
415 0           $key => $value
416             } split m{\n}, $result ;
417              
418 0 0         return wantarray ? %result : \%result ;
419             }
420              
421             =head3 B
422              
423             =head3 B
424              
425             =head3 B
426              
427             =head3 B
428              
429             =head3 B
430              
431             Stubs
432              
433             =cut
434              
435       0 1   sub endpoint_activate { }
436       0 1   sub endpoint_add { }
437       0 1   sub endpoint_deactivate { }
438       0 1   sub endpoint_modify { }
439       0 1   sub endpoint_rename { }
440              
441             =head2 OTHER
442              
443             =head3 B
444              
445             =head3 B
446              
447             =head3 B
448              
449             =head3 B
450              
451             =head3 B
452              
453             profile() returns information about the Globus user, including the email address
454             and public key.
455              
456             Otherwise stubs
457              
458             =cut
459              
460             sub profile {
461 0     0 1   my ($self) = @_ ;
462 0           my $command = qq{profile} ;
463             my $result
464 0           = _globus_action( $command, $self->{username}, $self->{key_path} ) ;
465             my %output
466 0           = map { my ( $k, $v ) = split m{:\s?}, $_ ; $k => $v } split m{\n},
  0            
  0            
467             $result ;
468 0 0         return wantarray ? %output : \%output ;
469             }
470              
471       0 1   sub help { }
472       0 1   sub history { }
473       0 1   sub man { }
474       0 1   sub versions { }
475              
476             sub _globus_action {
477 0     0     my ( $command, $user, $key_path ) = @_ ;
478 0           my $host = '@cli.globusonline.org' ;
479              
480 0           my $ssh = Net::OpenSSH->new(
481             $user . $host,
482             key_path => $key_path,
483             async => 0,
484             ) ;
485              
486 0 0         $ssh->error
487             and die "Couldn't establish SSH connection: " . $ssh->error ;
488              
489 0           my $debug = 0 ;
490              
491 0 0         say STDERR "\t" . '=' x 20 if $debug ;
492 0 0         say STDERR "\t" . $command if $debug ;
493 0 0         say STDERR "\t" . '-' x 20 if $debug ;
494              
495 0 0         my $response = $ssh->capture($command)
496             or carp "remote command failed: " . $ssh->error ;
497              
498 0           return $response ;
499             }
500              
501             1 ;
502              
503             =head1 LICENSE
504              
505             Copyright (C) 2017, Dave Jacoby.
506              
507             This program is free software, you can redistribute it and/or modify it
508             under the terms of the Artistic License version 2.0.
509              
510             =head1 AUTHOR
511              
512             Dave Jacoby - L
513              
514             =cut