File Coverage

blib/lib/Alien/Build/Plugin/Download/GitHub.pm
Criterion Covered Total %
statement 79 95 83.1
branch 28 40 70.0
condition 8 15 53.3
subroutine 15 15 100.0
pod 1 1 100.0
total 131 166 78.9


line stmt bran cond sub pod time code
1             package Alien::Build::Plugin::Download::GitHub;
2              
3 2     2   207144 use strict;
  2         11  
  2         55  
4 2     2   11 use warnings;
  2         4  
  2         88  
5 2     2   50 use 5.008001;
  2         7  
6 2     2   11 use Carp qw( croak );
  2         3  
  2         132  
7 2     2   1390 use Path::Tiny qw( path );
  2         20761  
  2         133  
8 2     2   1424 use JSON::PP qw( decode_json );
  2         25019  
  2         158  
9 2     2   1175 use URI;
  2         8244  
  2         69  
10 2     2   956 use Alien::Build::Plugin;
  2         20550  
  2         16  
11 2     2   1154 use Alien::Build::Plugin::Download::Negotiate;
  2         5578  
  2         21  
12 2     2   924 use Alien::Build::Plugin::Extract::Negotiate;
  2         64424  
  2         19  
13              
14             # ABSTRACT: Alien::Build plugin to download from GitHub
15             our $VERSION = '0.08'; # VERSION
16              
17              
18             has github_user => sub { croak("github_user is required") };
19             has github_repo => sub { croak("github_repo is required") };
20             has include_assets => 0;
21             has version => qr/^v?(.*)$/;
22             has prefer => 0;
23             has tags_only => 0;
24              
25             my $once = 1;
26              
27             sub init
28             {
29 7     7 1 199093 my($self, $meta) = @_;
30              
31 7 50       25 if(defined $meta->prop->{start_url})
32             {
33 0         0 croak("Don't set set a start_url with the Download::GitHub plugin");
34             }
35              
36 7 100       59 my $endpoint = $self->tags_only ? 'tags' : 'releases' ;
37 7   33     73 $meta->prop->{start_url} ||= "https://api.github.com/repos/@{[ $self->github_user ]}/@{[ $self->github_repo ]}/$endpoint";
  7         66  
  7         76  
38              
39 7         79 $meta->apply_plugin('Download',
40             prefer => $self->prefer,
41             version => $self->version,
42             );
43 7         60764 $meta->apply_plugin('Extract',
44             format => 'tar.gz',
45             );
46              
47 7         81714 my %gh_fetch_options;
48             my $secret;
49              
50 7         24 foreach my $name (qw( ALIEN_BUILD_GITHUB_TOKEN GITHUB_TOKEN GITHUB_PAT ))
51             {
52 21 50       65 if(defined $ENV{$name})
53             {
54 0         0 $secret = $ENV{$name};
55 0         0 push @{ $gh_fetch_options{http_headers} }, Authorization => "token $secret";
  0         0  
56 0 0       0 Alien::Build->log("using the GitHub Personal Access Token in $name") if $once;
57 0         0 $once = 0;
58 0         0 last;
59             }
60             }
61              
62             $meta->around_hook(
63             fetch => sub {
64 8     8   48421 my $orig = shift;
65 8         26 my($build, $url, @the_rest) = @_;
66              
67             # only do special stuff when talking to GitHub API. In particular, this
68             # avoids leaking the PAT (if specified) to other servers.
69             return $orig->($build, $url, @the_rest)
70 8 50       21 unless do {
71 8   66     55 my $uri = URI->new($url || $build->meta_prop->{start_url});
72 8 50       7923 $uri->host eq 'api.github.com' && $uri->scheme eq 'https';
73             };
74              
75             # Temporarily patch the log method so that we don't log the PAT
76 8         577 my $log = \&Alien::Build::log;
77 2     2   752 no warnings 'redefine';
  2         4  
  2         250  
78             local *Alien::Build::log = sub {
79 0 0       0 if(defined $secret)
80             {
81 0         0 $_[1] =~ s/\Q$secret\E/ '#' x length($secret) /eg;
  0         0  
82             }
83 0         0 goto &$log;
84 8         159 };
85 2     2   16 use warnings;
  2         5  
  2         965  
86              
87 8         46 my $res = $orig->($build, $url, @the_rest, %gh_fetch_options);
88 8 100 66     1108034 if($res->{type} eq 'file' && $res->{filename} =~ qr{^(?:releases|tags)$})
89             {
90 7         19 my $rel;
91 7 100       26 if($res->{content})
    50          
92             {
93 6         42 $rel = decode_json $res->{content};
94             }
95             elsif($res->{path})
96             {
97 1         7 $rel = decode_json path($res->{path})->slurp;
98             }
99             else
100             {
101 0         0 croak("malformed response object: no content or path");
102             }
103 7 100       106778 my $version_key = $res->{filename} eq 'releases' ? 'tag_name' : 'name';
104              
105 7 50       36 if($ENV{ALIEN_BUILD_PLUGIN_DOWNLOAD_GITHUB_DEBUG})
106             {
107 0         0 require YAML;
108 0   0     0 my $url = $url || $meta->prop->{start_url};
109 0         0 $url = URI->new($url);
110 0         0 $build->log(YAML::Dump({
111             url => $url->path,
112             res => $rel,
113             }));
114             }
115              
116             my $res2 = {
117             type => 'list',
118             list => [
119             map {
120 7         32 my $release = $_;
  18         33  
121 18         86 my($version) = $release->{$version_key} =~ $self->version;
122             my @results = ({
123             filename => $release->{$version_key},
124             url => $release->{tarball_url},
125 18 50       306 defined $version ? (version => $version) : (),
126             });
127              
128 18 100       55 if (my $include = $self->include_assets) {
129 4 100       35 my $filter = ref($include) eq 'Regexp' ? 1 : 0;
130 4 100       8 for my $asset(@{$release->{assets} || []}) {
  4         24  
131             push @results, {
132             asset_url => $asset->{url},
133             filename => $asset->{name},
134             url => $asset->{browser_download_url},
135             defined $version ? (version => $version) : (),
136 4 50 100     45 } if (0 == $filter or $asset->{name} =~ $include);
    100          
137             }
138             }
139 18         230 @results;
140             } @$rel
141             ],
142             };
143              
144 7 100       35 $res2->{protocol} = $res->{protocol} if exists $res->{protocol};
145 7         256 return $res2;
146             }
147             else
148             {
149 1         53 return $res;
150             }
151             },
152 7         69 );
153              
154 7 100       73 unless($self->prefer)
155             {
156             $meta->default_hook(
157             prefer => sub {
158 1     1   267 my($build, $res) = @_;
159 1         4 $res;
160             },
161 6         66 );
162             }
163              
164             }
165              
166             1;
167              
168             __END__