File Coverage

blib/lib/Web/AssetLib/OutputEngine/S3.pm
Criterion Covered Total %
statement 57 116 49.1
branch 0 26 0.0
condition 0 8 0.0
subroutine 19 21 90.4
pod n/a
total 76 171 44.4


line stmt bran cond sub pod time code
1             package Web::AssetLib::OutputEngine::S3;
2              
3             # ABSTRACT: AWS S3 output engine for Web::AssetLib
4             our $VERSION = "0.042";
5              
6 1     1   4123 use strict;
  1         4  
  1         67  
7 1     1   8 use warnings;
  1         5  
  1         65  
8 1     1   1318 use Kavorka;
  1         24206  
  1         9  
9 1     1   147829 use Moo;
  1         1  
  1         4  
10 1     1   177 use Carp;
  1         2  
  1         63  
11 1     1   707 use DateTime;
  1         318197  
  1         7  
12 1     1   2096 use Paws;
  1         767758  
  1         11  
13 1     1   760 use Paws::Credential::Environment;
  1         11883  
  1         11  
14 1     1   761 use Types::Standard qw/Str InstanceOf HashRef Maybe CodeRef/;
  1         62250  
  1         20  
15 1     1   2113 use Web::AssetLib::Output::Link;
  1         10152  
  1         9  
16 1     1   1370 use DateTime::Format::HTTP;
  1         5391  
  1         16  
17              
18             extends 'Web::AssetLib::OutputEngine';
19              
20 1     1   68 use feature 'switch';
  1         2  
  1         149  
21 1     1   5 no if $] >= 5.018, warnings => "experimental";
  1         2  
  1         9  
22              
23             has [qw/access_key secret_key/] => (
24             is => 'rw',
25             isa => Str,
26             required => 1
27             );
28              
29             has 'bucket_name' => (
30             is => 'rw',
31             isa => Str,
32             required => 1
33             );
34              
35             has 'region' => (
36             is => 'rw',
37             isa => Str,
38             required => 1
39             );
40              
41             # undef means no expiration tag is set
42             # callback recieves a hashref of put arguments
43             # and should return DateTime object
44             has 'object_expiration_cb' => (
45             is => 'rw',
46             isa => Maybe [CodeRef],
47             default => sub {
48             sub {
49             my $args = shift;
50             return DateTime->now( time_zone => 'local' )->add( years => 1 );
51             };
52             }
53             );
54              
55             has 'link_url' => (
56             is => 'rw',
57             isa => Str,
58             required => 1
59             );
60              
61             has 's3' => (
62             is => 'rw',
63             isa => InstanceOf ['Paws::S3'],
64             lazy => 1,
65             default => sub {
66             my $self = shift;
67             return Paws->service(
68             'S3',
69             credentials => Paws::Credential::Environment->new(
70             access_key => $self->access_key,
71             secret_key => $self->secret_key
72             ),
73             region => $self->region
74             );
75             }
76             );
77              
78             has '_s3_obj_cache' => (
79             is => 'lazy',
80             isa => HashRef,
81             clearer => '_invalidate_s3_obj_cache',
82             builder => '_build__s3_obj_cache'
83             );
84              
85 1 0 0 1   11658 method export (:$assets!, :$minifier?) {
  1 0 0 1   1  
  1 0   1   85  
  1 0   1   4  
  1 0   0   1  
  1         43  
  1         767  
  1         1062  
  1         4  
  1         77  
  1         2  
  1         960  
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
  0            
86 0           my $types = {};
87 0           my $output = [];
88              
89             # categorize into type groups, and seperate concatenated
90             # assets from those that stand alone
91              
92 0           foreach my $asset ( sort { $a->rank <=> $b->rank } @$assets ) {
  0            
93 0 0         if ( $asset->isPassthru ) {
94 0           push @$output,
95             Web::AssetLib::Output::Link->new(
96             src => $asset->link_path,
97             type => $asset->type
98             );
99             }
100             else {
101 0           for ( $asset->type ) {
102 0           when (/css|js/) {
103              
104             # should concatenate
105             $$types{ $asset->type }{_CONCAT_}
106 0           .= $asset->contents . "\n\r\n\r";
107             }
108 0           default {
109 0           $$types{ $asset->type }{ $asset->digest }
110             = $asset->contents;
111             }
112             }
113             }
114             }
115              
116 0           my $cacheInvalid = 0;
117 0           foreach my $type ( keys %$types ) {
118 0           foreach my $id ( keys %{ $$types{$type} } ) {
  0            
119 0           my $output_contents = $$types{$type}{$id};
120              
121 0 0         my $digest
122             = $id eq '_CONCAT_'
123             ? $self->generateDigest($output_contents)
124             : $id;
125              
126 0           my $filename = "assets/$digest.$type";
127 0 0         if ( $self->_s3_obj_cache->{$filename} ) {
128 0           $self->log->debug("found asset in cache: $filename");
129             }
130             else {
131 0           $self->log->debug("generating new asset: $filename");
132 0 0         if ($minifier) {
133 0           $output_contents = $minifier->minify(
134             contents => $output_contents,
135             type => $type
136             );
137             }
138              
139 0           my %putargs = (
140             Bucket => $self->bucket_name,
141             Key => $filename,
142             Body => $output_contents,
143             ContentType =>
144             Web::AssetLib::Util::normalizeMimeType($type)
145             );
146              
147 0 0         if ( $self->object_expiration_cb ) {
148             $putargs{Expires}
149 0           = DateTime::Format::HTTP->format_datetime(
150             $self->object_expiration_cb->( \%putargs ) );
151             }
152              
153 0           my $put = $self->s3->PutObject(%putargs);
154              
155 0           $cacheInvalid = 1;
156             }
157              
158 0           push @$output,
159             Web::AssetLib::Output::Link->new(
160             src => sprintf( '%s/%s', $self->link_url, $filename ),
161             type => $type
162             );
163             }
164             }
165              
166             $self->_invalidate_s3_obj_cache()
167 0 0         if $cacheInvalid;
168              
169 0           return $output;
170             }
171              
172 1 0   1   1670 method _build__s3_obj_cache (@_) {
  1 0   0   1  
  1         259  
  0            
  0            
  0            
173 0           my $results = $self->s3->ListObjects( Bucket => $self->bucket_name );
174 0           my $cache = { map { $_->Key => 1 } @{ $results->Contents } };
  0            
  0            
175              
176 0           $self->log->dump( 's3 object cache: ', $cache, 'debug' );
177              
178 0   0       return $cache || {};
179             }
180              
181             # before '_export' => sub {
182             # my $self = shift;
183             # my %args = @_;
184              
185             # $self->_validateLinks( bundle => $args{bundle} );
186             # };
187              
188             # # check css for links to other assets, and make
189             # # sure they are in the bundle
190             # method _validateLinks (:$bundle!) {
191             # foreach my $asset ( $bundle->allAssets ) {
192             # next unless $asset->contents;
193              
194             # if ( $asset->type eq 'css' ) {
195             # while ( $asset->contents =~ m/url\([",'](.+)[",']\)/g ) {
196             # $self->log->warn("css contains url: $1");
197             # }
198             # }
199             # }
200             # }
201              
202 1     1   6 no Moose;
  1         2  
  1         12  
203             1;
204             __END__
205              
206             =pod
207            
208             =encoding UTF-8
209            
210             =head1 NAME
211              
212             Web::AssetLib::OutputEngine::S3 - allows exporting an asset or bundle to an AWS S3 Bucket
213              
214             On first usage, a cache will be generated of all files in the bucket. This way, we know
215             what needs to be uploaded and what's already there.
216              
217             =head1 SYNOPSIS
218              
219             my $library = My::AssetLib::Library->new(
220             output_engines => [
221             Web::AssetLib::OutputEngine::S3->new(
222             access_key => 'AWS_ACCESS_KEY',
223             secret_key => 'AWS_SECRET_KEY',
224             bucket_name => 'S3_BUCKET_NAME',
225             region => 'S3_BUCKET_REGION'
226             )
227             ]
228             );
229              
230             $library->compile( ..., output_engine => 'S3' );
231              
232             =head1 USAGE
233              
234             This is an output engine plugin for L<Web::AssetLib>.
235              
236             Instantiate with C<< access_key >>, C<< secret_key >>, C<< bucket_name >>,
237             and C<< region >> arguments, and include in your library's output engine list.
238              
239             =head1 PARAMETERS
240            
241             =head2 access_key
242              
243             =head2 secret_key
244            
245             AWS access & secret keys. Must have C<List> and C<Put> permissions for destination bucket.
246             Required.
247              
248             =head2 bucket_name
249              
250             S3 bucket name. Required.
251              
252             =head2 region
253              
254             AWS region name of the bucket. Required.
255              
256             =head2 region
257              
258             AWS region name of the bucket
259              
260             =head2 link_url
261              
262             Used as the base url of any asset that gets exported to S3. Make sure it's public!
263             Your CDN may go here.
264              
265             =head2 object_expiration_cb
266              
267             Provide a coderef used to calculate the Expiration header. Currently,
268             no arguments are passed to the callback. Defaults to:
269              
270             sub {
271             return DateTime->now( time_zone => 'local' )->add( years => 1 );
272             };
273              
274             =head1 SEE ALSO
275              
276             L<Web::AssetLib>
277             L<Web::AssetLib::OutputEngine>
278              
279             =head1 AUTHOR
280            
281             Ryan Lang <rlang@cpan.org>
282              
283             =cut
284