aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Arter <davea@mysociety.org>2018-09-20 16:15:38 +0100
committerDave Arter <davea@mysociety.org>2018-09-28 16:19:47 +0100
commit07bc1188dc149e05b61e0d93ecf3ef1c26dc8690 (patch)
tree1ac3c9d0148b3f98ff29985e8c760740bb8d2548
parent561e01b9b51b62e2566d80cd63d308f9a4f82822 (diff)
Add S3 photo storage backend
-rw-r--r--CHANGELOG.md2
-rw-r--r--conf/general.yml-example7
-rw-r--r--cpanfile1
-rw-r--r--cpanfile.snapshot768
-rw-r--r--docs/customising/config.md197
-rw-r--r--docs/install/manual-install.md2
-rw-r--r--perllib/FixMyStreet.pm2
-rw-r--r--perllib/FixMyStreet/PhotoStorage/S3.pm122
-rw-r--r--t/app/model/photoset.t25
-rw-r--r--t/photostorage/s3.t164
10 files changed, 1204 insertions, 86 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10e0776b3..4bbc7604f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
## Releases
* Unreleased
+ - New features:
+ - Support for storing photos in AWS S3. #2253
- Front end improvements:
- Import end point can optionally return a web page #2225
- Clicking the "Report" header links on the homepage now focusses
diff --git a/conf/general.yml-example b/conf/general.yml-example
index 11fe654ff..11902c0b3 100644
--- a/conf/general.yml-example
+++ b/conf/general.yml-example
@@ -85,6 +85,13 @@ PHOTO_STORAGE_OPTIONS:
# BUCKET: 'fixmystreet-photos'
# ACCESS_KEY: ''
# SECRET_KEY: ''
+# PREFIX: '' # optional prefix for key names in bucket, e.g. if you
+ # have multiple FMS sites storing photos in the same bucket.
+# CREATE_BUCKET: 0 # optional, set to 1 if the S3 bucket should be created if
+ # it doesn't already exist. Requires the appropriate AWS
+ # permissions.
+# REGION: 'eu-west-1' # optional, only used if CREATE_BUCKET is set. Controls
+ # which AWS region the S3 bucket will be created in.
# Location of MapIt, to map points to administrative areas, and what types of
# area from it you want to use. If left blank, a default area will be used
diff --git a/cpanfile b/cpanfile
index 1bc2d8f09..71b3fc93e 100644
--- a/cpanfile
+++ b/cpanfile
@@ -79,6 +79,7 @@ requires 'Module::Pluggable';
requires 'Moose';
requires 'MooX::Types::MooseLike';
requires 'namespace::autoclean';
+requires 'Net::Amazon::S3';
requires 'Net::DNS::Resolver';
requires 'Net::Domain::TLD', '1.75';
requires 'Net::Facebook::Oauth2', '0.10';
diff --git a/cpanfile.snapshot b/cpanfile.snapshot
index 082be0ac0..565c389a6 100644
--- a/cpanfile.snapshot
+++ b/cpanfile.snapshot
@@ -877,6 +877,18 @@ DISTRIBUTIONS
Test::Fatal 0
Test::More 0
perl 5.006
+ Class-MethodMaker-2.24
+ pathname: S/SC/SCHWIGON/class-methodmaker/Class-MethodMaker-2.24.tar.gz
+ provides:
+ Class::MethodMaker 2.24
+ Class::MethodMaker::Constants undef
+ Class::MethodMaker::Engine 2.24
+ Class::MethodMaker::OptExt undef
+ Class::MethodMaker::V1Compat undef
+ Generate undef
+ requirements:
+ ExtUtils::MakeMaker 0
+ perl 5.006
Class-Mix-0.005
pathname: Z/ZE/ZEFRAM/Class-Mix-0.005.tar.gz
provides:
@@ -1595,6 +1607,30 @@ DISTRIBUTIONS
autodie 2.00
strict 0
warnings 0
+ Data-Stream-Bulk-0.11
+ pathname: D/DO/DOY/Data-Stream-Bulk-0.11.tar.gz
+ provides:
+ Data::Stream::Bulk 0.11
+ Data::Stream::Bulk::Array 0.11
+ Data::Stream::Bulk::Callback 0.11
+ Data::Stream::Bulk::Cat 0.11
+ Data::Stream::Bulk::Chunked 0.11
+ Data::Stream::Bulk::DBI 0.11
+ Data::Stream::Bulk::DBIC 0.11
+ Data::Stream::Bulk::DoneFlag 0.11
+ Data::Stream::Bulk::FileHandle 0.11
+ Data::Stream::Bulk::Filter 0.11
+ Data::Stream::Bulk::Nil 0.11
+ Data::Stream::Bulk::Path::Class 0.11
+ Data::Stream::Bulk::Util 0.11
+ requirements:
+ ExtUtils::MakeMaker 6.30
+ Moose 0.90
+ Path::Class 0
+ Sub::Exporter 0
+ Test::More 0.88
+ Test::Requires 0
+ namespace::clean 0
Data-Visitor-0.28
pathname: D/DO/DOY/Data-Visitor-0.28.tar.gz
provides:
@@ -1641,6 +1677,23 @@ DISTRIBUTIONS
utf8 0
vars 0
warnings 0
+ DateTime-Event-ICal-0.13
+ pathname: F/FG/FGLOCK/DateTime-Event-ICal-0.13.tar.gz
+ provides:
+ DateTime::Event::ICal 0.13
+ requirements:
+ DateTime 0
+ DateTime::Event::Recurrence 0.11
+ ExtUtils::MakeMaker 0
+ DateTime-Event-Recurrence-0.19
+ pathname: F/FG/FGLOCK/DateTime-Event-Recurrence-0.19.tar.gz
+ provides:
+ DateTime::Event::Recurrence 0.19
+ DateTime::Set::ICal 0.19
+ requirements:
+ DateTime 0.27
+ DateTime::Set 0.3600
+ ExtUtils::MakeMaker 0
DateTime-Format-Builder-0.80
pathname: D/DR/DROLSKY/DateTime-Format-Builder-0.80.tar.gz
provides:
@@ -1655,9 +1708,24 @@ DISTRIBUTIONS
Class::Factory::Util 1.6
DateTime 0.12
DateTime::Format::Strptime 1.04
- Module::Build 0.36
+ Module::Build 0
Params::Validate 0.72
Task::Weaken 0
+ DateTime-Format-Flexible-0.31
+ pathname: T/TH/THINC/DateTime-Format-Flexible-0.31.tar.gz
+ provides:
+ DateTime::Format::Flexible 0.31
+ DateTime::Format::Flexible::lang undef
+ DateTime::Format::Flexible::lang::de undef
+ DateTime::Format::Flexible::lang::en undef
+ DateTime::Format::Flexible::lang::es undef
+ requirements:
+ DateTime 0
+ DateTime::Format::Builder 0.74
+ DateTime::TimeZone 0
+ ExtUtils::MakeMaker 0
+ List::MoreUtils 0
+ Module::Pluggable 0
DateTime-Format-HTTP-0.40
pathname: C/CK/CKRAS/DateTime-Format-HTTP-0.40.tar.gz
provides:
@@ -1667,6 +1735,17 @@ DISTRIBUTIONS
HTTP::Date 1.44
Module::Build 0.36
Test::More 0.47
+ DateTime-Format-ICal-0.09
+ pathname: D/DR/DROLSKY/DateTime-Format-ICal-0.09.tar.gz
+ provides:
+ DateTime::Format::ICal 0.09
+ requirements:
+ DateTime 0.17
+ DateTime::Event::ICal 0.03
+ DateTime::Set 0.1
+ DateTime::TimeZone 0.22
+ Module::Build 0
+ Params::Validate 0.59
DateTime-Format-ISO8601-0.08
pathname: J/JH/JHOBLITT/DateTime-Format-ISO8601-0.08.tar.gz
provides:
@@ -1684,6 +1763,44 @@ DISTRIBUTIONS
Module::Build 0
Params::Validate 0.67
Test::More 0.47
+ DateTime-Format-Natural-1.05
+ pathname: S/SC/SCHUBIGER/DateTime-Format-Natural-1.05.tar.gz
+ provides:
+ DateTime::Format::Natural 1.05
+ DateTime::Format::Natural::Calc 1.41
+ DateTime::Format::Natural::Compat 0.07
+ DateTime::Format::Natural::Duration 0.06
+ DateTime::Format::Natural::Duration::Checks 0.04
+ DateTime::Format::Natural::Expand 0.03
+ DateTime::Format::Natural::Extract 0.11
+ DateTime::Format::Natural::Formatted 0.07
+ DateTime::Format::Natural::Helpers 0.06
+ DateTime::Format::Natural::Lang::Base 1.08
+ DateTime::Format::Natural::Lang::EN 1.62
+ DateTime::Format::Natural::Rewrite 0.06
+ DateTime::Format::Natural::Test 0.10
+ DateTime::Format::Natural::Utils 0.05
+ DateTime::Format::Natural::Wrappers 0.03
+ requirements:
+ Carp 0
+ Clone 0
+ Cwd 0
+ DateTime 0
+ DateTime::TimeZone 0
+ Exporter 0
+ File::Find 0
+ File::Spec 0
+ FindBin 0
+ Getopt::Long 0
+ List::MoreUtils 0
+ Module::Util 0
+ Params::Validate 0
+ Scalar::Util 0
+ Storable 0
+ Term::ReadLine 0
+ Test::MockTime 0
+ Test::More 0
+ boolean 0
DateTime-Format-Pg-0.16008
pathname: D/DM/DMAKI/DateTime-Format-Pg-0.16008.tar.gz
provides:
@@ -2191,9 +2308,21 @@ DISTRIBUTIONS
DateTime::Locale::zu_ZA undef
requirements:
List::MoreUtils 0
- Module::Build 0
+ Module::Build 0.36
Params::Validate 0.91
perl 5.006
+ DateTime-Set-0.3900
+ pathname: F/FG/FGLOCK/DateTime-Set-0.3900.tar.gz
+ provides:
+ DateTime::Set 0.3900
+ DateTime::Span undef
+ DateTime::SpanSet undef
+ Set::Infinite::_recurrence undef
+ requirements:
+ DateTime 0.12
+ Params::Validate 0
+ Set::Infinite 0.59
+ Test::More 0
DateTime-TimeZone-2.18
pathname: D/DR/DROLSKY/DateTime-TimeZone-2.18.tar.gz
provides:
@@ -2589,6 +2718,20 @@ DISTRIBUTIONS
perl 5.008004
strict 0
warnings 0
+ DateTimeX-Easy-0.089
+ pathname: R/RO/ROKR/DateTimeX-Easy-0.089.tar.gz
+ provides:
+ DateTimeX::Easy 0.089
+ requirements:
+ Date::Parse 0
+ DateTime 0
+ DateTime::Format::Flexible 0
+ DateTime::Format::ICal 0
+ DateTime::Format::Natural 0
+ ExtUtils::MakeMaker 6.31
+ Scalar::Util 0
+ Test::Most 0
+ Time::Zone 0
Devel-Caller-2.06
pathname: R/RC/RCLAMP/Devel-Caller-2.06.tar.gz
provides:
@@ -2648,6 +2791,14 @@ DISTRIBUTIONS
Digest::SHA 1
ExtUtils::MakeMaker 0
perl 5.004
+ Digest-MD5-File-0.08
+ pathname: D/DM/DMUEY/Digest-MD5-File-0.08.tar.gz
+ provides:
+ Digest::MD5::File 0.08
+ requirements:
+ Digest::MD5 0
+ ExtUtils::MakeMaker 0
+ LWP::UserAgent 0
Digest-Perl-MD5-1.9
pathname: D/DE/DELTA/Digest-Perl-MD5-1.9.tar.gz
provides:
@@ -2934,6 +3085,16 @@ DISTRIBUTIONS
perl 5.008001
strict 0
warnings 0
+ Exporter-Lite-0.08
+ pathname: N/NE/NEILB/Exporter-Lite-0.08.tar.gz
+ provides:
+ Exporter::Lite 0.08
+ requirements:
+ Carp 0
+ ExtUtils::MakeMaker 6.3
+ perl 5.006
+ strict 0
+ warnings 0
Exporter-Tiny-0.042
pathname: T/TO/TOBYINK/Exporter-Tiny-0.042.tar.gz
provides:
@@ -3472,6 +3633,14 @@ DISTRIBUTIONS
File::Temp 0
Scalar::Util 0
Test::More 0.88
+ IO-Interactive-1.022
+ pathname: B/BD/BDFOY/IO-Interactive-1.022.tar.gz
+ provides:
+ IO::Interactive 1.022
+ requirements:
+ ExtUtils::MakeMaker 6.64
+ File::Spec::Functions 0
+ perl 5.008
IO-SessionData-1.03
pathname: P/PH/PHRED/IO-SessionData-1.03.tar.gz
provides:
@@ -3601,6 +3770,13 @@ DISTRIBUTIONS
Mozilla::CA 20110101
Net::HTTPS 6
perl 5.008001
+ LWP-UserAgent-Determined-1.07
+ pathname: A/AL/ALEXMV/LWP-UserAgent-Determined-1.07.tar.gz
+ provides:
+ LWP::UserAgent::Determined 1.07
+ requirements:
+ ExtUtils::MakeMaker 0
+ LWP 0
LWPx-Profile-0.2
pathname: C/CR/CREIN/LWPx-Profile-0.2.tar.gz
provides:
@@ -4074,6 +4250,14 @@ DISTRIBUTIONS
Text::ParseWords 0
perl 5.008001
version 0
+ Module-Util-1.09
+ pathname: M/MA/MATTLAW/Module-Util-1.09.tar.gz
+ provides:
+ Module::Util 1.09
+ requirements:
+ Module::Build 0.40
+ Test::More 0
+ perl v5.5.3
Moo-2.003001
pathname: H/HA/HAARG/Moo-2.003001.tar.gz
provides:
@@ -4391,6 +4575,22 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 6.31
Moose 1.16
Test::More 0.88
+ MooseX-StrictConstructor-0.21
+ pathname: D/DR/DROLSKY/MooseX-StrictConstructor-0.21.tar.gz
+ provides:
+ MooseX::StrictConstructor 0.21
+ MooseX::StrictConstructor::Trait::Class 0.21
+ MooseX::StrictConstructor::Trait::Method::Constructor 0.21
+ requirements:
+ B 0
+ ExtUtils::MakeMaker 0
+ Moose 0.94
+ Moose::Exporter 0
+ Moose::Role 0
+ Moose::Util::MetaRole 0
+ namespace::autoclean 0
+ strict 0
+ warnings 0
MooseX-Traits-Pluggable-0.10
pathname: R/RK/RKITOVER/MooseX-Traits-Pluggable-0.10.tar.gz
provides:
@@ -4431,6 +4631,43 @@ DISTRIBUTIONS
Test::More 0.88
Test::Requires 0
namespace::clean 0.19
+ MooseX-Types-DateTime-0.13
+ pathname: E/ET/ETHER/MooseX-Types-DateTime-0.13.tar.gz
+ provides:
+ MooseX::Types::DateTime 0.13
+ requirements:
+ DateTime 0.4302
+ DateTime::Duration 0.4302
+ DateTime::Locale 0.4001
+ DateTime::TimeZone 0.95
+ Module::Build::Tiny 0.034
+ Moose 0.41
+ MooseX::Types 0.30
+ MooseX::Types::Moose 0.30
+ if 0
+ namespace::clean 0.19
+ perl 5.008003
+ strict 0
+ warnings 0
+ MooseX-Types-DateTime-MoreCoercions-0.15
+ pathname: E/ET/ETHER/MooseX-Types-DateTime-MoreCoercions-0.15.tar.gz
+ provides:
+ MooseX::Types::DateTime::MoreCoercions 0.15
+ requirements:
+ DateTime 0.4302
+ DateTime::Duration 0.4302
+ DateTimeX::Easy 0.085
+ Module::Build::Tiny 0.007
+ Moose 0.41
+ MooseX::Types 0.04
+ MooseX::Types::DateTime 0.07
+ MooseX::Types::Moose 0.04
+ Time::Duration::Parse 0.06
+ if 0
+ namespace::clean 0.19
+ perl 5.006
+ strict 0
+ warnings 0
MooseX-Types-Path-Class-0.06
pathname: T/TH/THEPLER/MooseX-Types-Path-Class-0.06.tar.gz
provides:
@@ -4450,6 +4687,127 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 0
Test 0
perl 5.006
+ Net-Amazon-S3-0.85
+ pathname: L/LL/LLAP/Net-Amazon-S3-0.85.tar.gz
+ provides:
+ Net::Amazon::S3 0.85
+ Net::Amazon::S3::Bucket 0.85
+ Net::Amazon::S3::Client 0.85
+ Net::Amazon::S3::Client::Bucket 0.85
+ Net::Amazon::S3::Client::Object 0.85
+ Net::Amazon::S3::HTTPRequest 0.85
+ Net::Amazon::S3::Request 0.85
+ Net::Amazon::S3::Request::AbortMultipartUpload 0.85
+ Net::Amazon::S3::Request::Bucket 0.85
+ Net::Amazon::S3::Request::CompleteMultipartUpload 0.85
+ Net::Amazon::S3::Request::CreateBucket 0.85
+ Net::Amazon::S3::Request::DeleteBucket 0.85
+ Net::Amazon::S3::Request::DeleteMultiObject 0.85
+ Net::Amazon::S3::Request::DeleteObject 0.85
+ Net::Amazon::S3::Request::GetBucketAccessControl 0.85
+ Net::Amazon::S3::Request::GetBucketLocationConstraint 0.85
+ Net::Amazon::S3::Request::GetObject 0.85
+ Net::Amazon::S3::Request::GetObjectAccessControl 0.85
+ Net::Amazon::S3::Request::InitiateMultipartUpload 0.85
+ Net::Amazon::S3::Request::ListAllMyBuckets 0.85
+ Net::Amazon::S3::Request::ListBucket 0.85
+ Net::Amazon::S3::Request::ListParts 0.85
+ Net::Amazon::S3::Request::Object 0.85
+ Net::Amazon::S3::Request::PutObject 0.85
+ Net::Amazon::S3::Request::PutPart 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Header 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Header::Acl_short 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Header::Content_length 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Header::Content_md5 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Header::Content_type 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Header::Copy_source 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Header::Encryption 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Method 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Method::DELETE 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Method::GET 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Method::POST 0.85
+ Net::Amazon::S3::Request::Role::HTTP::Method::PUT 0.85
+ Net::Amazon::S3::Request::Role::Query::Action 0.85
+ Net::Amazon::S3::Request::Role::Query::Action::Acl 0.85
+ Net::Amazon::S3::Request::Role::Query::Action::Delete 0.85
+ Net::Amazon::S3::Request::Role::Query::Action::Location 0.85
+ Net::Amazon::S3::Request::Role::Query::Action::Uploads 0.85
+ Net::Amazon::S3::Request::Role::Query::Param 0.85
+ Net::Amazon::S3::Request::Role::Query::Param::Delimiter 0.85
+ Net::Amazon::S3::Request::Role::Query::Param::Marker 0.85
+ Net::Amazon::S3::Request::Role::Query::Param::Max_keys 0.85
+ Net::Amazon::S3::Request::Role::Query::Param::Part_number 0.85
+ Net::Amazon::S3::Request::Role::Query::Param::Prefix 0.85
+ Net::Amazon::S3::Request::Role::Query::Param::Upload_id 0.85
+ Net::Amazon::S3::Request::Service 0.85
+ Net::Amazon::S3::Request::SetBucketAccessControl 0.85
+ Net::Amazon::S3::Request::SetObjectAccessControl 0.85
+ Net::Amazon::S3::Role::Bucket 0.85
+ Net::Amazon::S3::Signature 0.85
+ Net::Amazon::S3::Signature::V2 0.85
+ Net::Amazon::S3::Signature::V4 0.85
+ Net::Amazon::S3::Signature::V4Implementation 0.19
+ Shared::Examples::Net::Amazon::S3 0.85
+ Shared::Examples::Net::Amazon::S3::ACL 0.85
+ Shared::Examples::Net::Amazon::S3::API 0.85
+ Shared::Examples::Net::Amazon::S3::Client 0.85
+ Shared::Examples::Net::Amazon::S3::Error 0.85
+ Shared::Examples::Net::Amazon::S3::Operation::Bucket::Create 0.85
+ Shared::Examples::Net::Amazon::S3::Operation::Bucket::Objects::Delete 0.85
+ Shared::Examples::Net::Amazon::S3::Operation::Bucket::Objects::List 0.85
+ Shared::Examples::Net::Amazon::S3::Operation::Service::Buckets::List 0.85
+ Shared::Examples::Net::Amazon::S3::Request 0.85
+ requirements:
+ Carp 0
+ Data::Stream::Bulk::Callback 0
+ DateTime::Format::HTTP 0
+ Digest::HMAC_SHA1 0
+ Digest::MD5 0
+ Digest::MD5::File 0
+ Digest::SHA 0
+ Exporter::Tiny 0
+ ExtUtils::MakeMaker 0
+ File::Find::Rule 0
+ File::stat 0
+ Getopt::Long 0
+ HTTP::Date 0
+ HTTP::Response 0
+ HTTP::Status 0
+ Hash::Util 0
+ IO::File 1.14
+ LWP 6.03
+ LWP::UserAgent::Determined 0
+ MIME::Base64 0
+ MIME::Types 0
+ Moose 0.85
+ Moose::Object 0
+ Moose::Role 0
+ Moose::Util 0
+ Moose::Util::TypeConstraints 0
+ MooseX::Role::Parameterized 0
+ MooseX::StrictConstructor 0.16
+ MooseX::Types::DateTime::MoreCoercions 0.07
+ Path::Class 0
+ Pod::Usage 0
+ Ref::Util 0
+ Regexp::Common 0
+ Scalar::Util 0
+ Sub::Override 0
+ Term::Encoding 0
+ Term::ProgressBar::Simple 0
+ Test::Deep 0
+ Test::More 0
+ Time::Piece 0
+ URI 0
+ URI::Escape 0
+ URI::QueryParam 0
+ XML::LibXML 0
+ XML::LibXML::XPathContext 0
+ namespace::clean 0
+ parent 0
+ sort 0
+ strict 0
+ warnings 0
Net-DNS-0.72
pathname: N/NL/NLNETLABS/Net-DNS-0.72.tar.gz
provides:
@@ -4671,7 +5029,7 @@ DISTRIBUTIONS
LWP::Protocol::https 0
LWP::UserAgent 2.032
Module::Build::Tiny 0.034
- Net::HTTP 0
+ Net::HTTP >= 0, != 6.04, != 6.05
Net::Netrc 0
URI 1.40
URI::Escape 0
@@ -5491,6 +5849,26 @@ DISTRIBUTIONS
Readonly::Scalar 1.03
requirements:
ExtUtils::MakeMaker 0
+ Ref-Util-0.204
+ pathname: A/AR/ARC/Ref-Util-0.204.tar.gz
+ provides:
+ Ref::Util 0.204
+ Ref::Util::PP 0.204
+ requirements:
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ Ref::Util::XS 0
+ Text::ParseWords 0
+ perl 5.006
+ Ref-Util-XS-0.117
+ pathname: X/XS/XSAWYERX/Ref-Util-XS-0.117.tar.gz
+ provides:
+ Ref::Util::XS 0.117
+ requirements:
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ XSLoader 0
+ perl 5.006
Regexp-Common-2013031301
pathname: A/AB/ABIGAIL/Regexp-Common-2013031301.tar.gz
provides:
@@ -5789,6 +6167,16 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 0
Test::More 0
perl 5.006001
+ Set-Infinite-0.65
+ pathname: F/FG/FGLOCK/Set-Infinite-0.65.tar.gz
+ provides:
+ Set::Infinite 0.65
+ Set::Infinite::Arithmetic undef
+ Set::Infinite::Basic undef
+ requirements:
+ ExtUtils::MakeMaker 0
+ Test::More 0
+ Time::Local 0
Snowball-Norwegian-1.2
pathname: A/AS/ASKSH/Snowball-Norwegian-1.2.tar.gz
provides:
@@ -6208,6 +6596,53 @@ DISTRIBUTIONS
File::Spec 0.8
File::Temp 0.12
Scalar::Util 0
+ Term-Encoding-0.02
+ pathname: M/MI/MIYAGAWA/Term-Encoding-0.02.tar.gz
+ provides:
+ Term::Encoding 0.02
+ requirements:
+ ExtUtils::MakeMaker 0
+ Term-ProgressBar-2.22
+ pathname: M/MA/MANWAR/Term-ProgressBar-2.22.tar.gz
+ provides:
+ Term::ProgressBar 2.22
+ Term::ProgressBar::IO 2.22
+ requirements:
+ Capture::Tiny 0.13
+ Carp 0
+ Class::MethodMaker 1.02
+ ExtUtils::MakeMaker 0
+ Fatal 0
+ File::Temp 0
+ POSIX 0
+ Term::ReadKey 2.14
+ Test::Exception 0.31
+ Test::More 0.80
+ Test::Warnings 0
+ perl 5.006
+ Term-ProgressBar-Quiet-0.31
+ pathname: L/LB/LBROCARD/Term-ProgressBar-Quiet-0.31.tar.gz
+ provides:
+ Term::ProgressBar::Quiet 0.31
+ requirements:
+ ExtUtils::MakeMaker 0
+ IO::Interactive 0
+ Term::ProgressBar 0
+ Test::MockObject 0
+ Test::More 0
+ Term-ProgressBar-Simple-0.03
+ pathname: E/EV/EVDB/Term-ProgressBar-Simple-0.03.tar.gz
+ provides:
+ Term::ProgressBar::Simple 0.03
+ requirements:
+ ExtUtils::MakeMaker 0
+ Term::ProgressBar::Quiet 0
+ TermReadKey-2.37
+ pathname: J/JS/JSTOWE/TermReadKey-2.37.tar.gz
+ provides:
+ Term::ReadKey 2.37
+ requirements:
+ ExtUtils::MakeMaker 6.58
Test-Base-0.60
pathname: I/IN/INGY/Test-Base-0.60.tar.gz
provides:
@@ -6239,10 +6674,10 @@ DISTRIBUTIONS
Test::More 0.88
strict 0
warnings 0
- Test-Deep-0.110
- pathname: R/RJ/RJBS/Test-Deep-0.110.tar.gz
+ Test-Deep-1.128
+ pathname: R/RJ/RJBS/Test-Deep-1.128.tar.gz
provides:
- Test::Deep 0.110
+ Test::Deep 1.128
Test::Deep::All undef
Test::Deep::Any undef
Test::Deep::Array undef
@@ -6268,11 +6703,14 @@ DISTRIBUTIONS
Test::Deep::MM undef
Test::Deep::Methods undef
Test::Deep::NoTest undef
+ Test::Deep::None undef
Test::Deep::Number undef
+ Test::Deep::Obj undef
Test::Deep::Ref undef
Test::Deep::RefType undef
Test::Deep::Regexp undef
Test::Deep::RegexpMatches undef
+ Test::Deep::RegexpOnly undef
Test::Deep::RegexpRef undef
Test::Deep::RegexpRefOnly undef
Test::Deep::RegexpVersion undef
@@ -6294,30 +6732,32 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 0
List::Util 1.09
Scalar::Util 1.09
- Test::More 0
- Test::NoWarnings 0.02
- Test::Tester 0.04
- Test-Differences-0.61
- pathname: O/OV/OVID/Test-Differences-0.61.tar.gz
+ Test::Builder 0
+ Test-Differences-0.64
+ pathname: D/DC/DCANTRELL/Test-Differences-0.64.tar.gz
provides:
- Test::Differences 0.61
+ Test::Differences 0.64
requirements:
+ Capture::Tiny 0.24
Data::Dumper 2.126
- Module::Build 0.36
- Test::More 0
+ Test::More 0.88
Text::Diff 0.35
- Test-Exception-0.31
- pathname: A/AD/ADIE/Test-Exception-0.31.tar.gz
+ Test-Exception-0.43
+ pathname: E/EX/EXODIST/Test-Exception-0.43.tar.gz
provides:
- Test::Exception 0.31
+ Test::Exception 0.43
requirements:
- Module::Build 0.36
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
Sub::Uplevel 0.18
Test::Builder 0.7
Test::Builder::Tester 1.07
Test::Harness 2.03
- Test::More 0.7
- Test::Simple 0.7
+ base 0
+ perl 5.006001
+ strict 0
+ warnings 0
Test-Fatal-0.010
pathname: R/RJ/RJBS/Test-Fatal-0.010.tar.gz
provides:
@@ -6333,57 +6773,58 @@ DISTRIBUTIONS
overload 0
strict 0
warnings 0
- Test-Harness-3.32
- pathname: L/LE/LEONT/Test-Harness-3.32.tar.gz
- provides:
- App::Prove 3.32
- App::Prove::State 3.32
- App::Prove::State::Result 3.32
- App::Prove::State::Result::Test 3.32
- TAP::Base 3.32
- TAP::Formatter::Base 3.32
- TAP::Formatter::Color 3.32
- TAP::Formatter::Console 3.32
- TAP::Formatter::Console::ParallelSession 3.32
- TAP::Formatter::Console::Session 3.32
- TAP::Formatter::File 3.32
- TAP::Formatter::File::Session 3.32
- TAP::Formatter::Session 3.32
- TAP::Harness 3.32
- TAP::Harness::Env 3.32
- TAP::Object 3.32
- TAP::Parser 3.32
- TAP::Parser::Aggregator 3.32
- TAP::Parser::Grammar 3.32
- TAP::Parser::Iterator 3.32
- TAP::Parser::Iterator::Array 3.32
- TAP::Parser::Iterator::Process 3.32
- TAP::Parser::Iterator::Stream 3.32
- TAP::Parser::IteratorFactory 3.32
- TAP::Parser::Multiplexer 3.32
- TAP::Parser::Result 3.32
- TAP::Parser::Result::Bailout 3.32
- TAP::Parser::Result::Comment 3.32
- TAP::Parser::Result::Plan 3.32
- TAP::Parser::Result::Pragma 3.32
- TAP::Parser::Result::Test 3.32
- TAP::Parser::Result::Unknown 3.32
- TAP::Parser::Result::Version 3.32
- TAP::Parser::Result::YAML 3.32
- TAP::Parser::ResultFactory 3.32
- TAP::Parser::Scheduler 3.32
- TAP::Parser::Scheduler::Job 3.32
- TAP::Parser::Scheduler::Spinner 3.32
- TAP::Parser::Source 3.32
- TAP::Parser::SourceHandler 3.32
- TAP::Parser::SourceHandler::Executable 3.32
- TAP::Parser::SourceHandler::File 3.32
- TAP::Parser::SourceHandler::Handle 3.32
- TAP::Parser::SourceHandler::Perl 3.32
- TAP::Parser::SourceHandler::RawTAP 3.32
- TAP::Parser::YAMLish::Reader 3.32
- TAP::Parser::YAMLish::Writer 3.32
- Test::Harness 3.32
+ Test-Harness-3.42
+ pathname: L/LE/LEONT/Test-Harness-3.42.tar.gz
+ provides:
+ App::Prove 3.42
+ App::Prove::State 3.42
+ App::Prove::State::Result 3.42
+ App::Prove::State::Result::Test 3.42
+ Harness::Hook undef
+ TAP::Base 3.42
+ TAP::Formatter::Base 3.42
+ TAP::Formatter::Color 3.42
+ TAP::Formatter::Console 3.42
+ TAP::Formatter::Console::ParallelSession 3.42
+ TAP::Formatter::Console::Session 3.42
+ TAP::Formatter::File 3.42
+ TAP::Formatter::File::Session 3.42
+ TAP::Formatter::Session 3.42
+ TAP::Harness 3.42
+ TAP::Harness::Env 3.42
+ TAP::Object 3.42
+ TAP::Parser 3.42
+ TAP::Parser::Aggregator 3.42
+ TAP::Parser::Grammar 3.42
+ TAP::Parser::Iterator 3.42
+ TAP::Parser::Iterator::Array 3.42
+ TAP::Parser::Iterator::Process 3.42
+ TAP::Parser::Iterator::Stream 3.42
+ TAP::Parser::IteratorFactory 3.42
+ TAP::Parser::Multiplexer 3.42
+ TAP::Parser::Result 3.42
+ TAP::Parser::Result::Bailout 3.42
+ TAP::Parser::Result::Comment 3.42
+ TAP::Parser::Result::Plan 3.42
+ TAP::Parser::Result::Pragma 3.42
+ TAP::Parser::Result::Test 3.42
+ TAP::Parser::Result::Unknown 3.42
+ TAP::Parser::Result::Version 3.42
+ TAP::Parser::Result::YAML 3.42
+ TAP::Parser::ResultFactory 3.42
+ TAP::Parser::Scheduler 3.42
+ TAP::Parser::Scheduler::Job 3.42
+ TAP::Parser::Scheduler::Spinner 3.42
+ TAP::Parser::Source 3.42
+ TAP::Parser::SourceHandler 3.42
+ TAP::Parser::SourceHandler::Executable 3.42
+ TAP::Parser::SourceHandler::File 3.42
+ TAP::Parser::SourceHandler::Handle 3.42
+ TAP::Parser::SourceHandler::Perl 3.42
+ TAP::Parser::SourceHandler::RawTAP 3.42
+ TAP::Parser::YAMLish::Reader 3.42
+ TAP::Parser::YAMLish::Writer 3.42
+ Test::Harness 3.42
requirements:
ExtUtils::MakeMaker 0
Test-LongString-0.15
@@ -6437,6 +6878,20 @@ DISTRIBUTIONS
Test::More 0
Time::Local 0
Time::Piece 1.08
+ Test-Most-0.35
+ pathname: O/OV/OVID/Test-Most-0.35.tar.gz
+ provides:
+ Test::Most 0.35
+ Test::Most::Exception 0.35
+ requirements:
+ Exception::Class 1.14
+ ExtUtils::MakeMaker 0
+ Test::Deep 0.119
+ Test::Differences 0.64
+ Test::Exception 0.43
+ Test::Harness 3.35
+ Test::More 1.302047
+ Test::Warn 0.30
Test-NoWarnings-1.04
pathname: A/AD/ADAMK/Test-NoWarnings-1.04.tar.gz
provides:
@@ -6514,6 +6969,83 @@ DISTRIBUTIONS
Test::Requires 0
Time::HiRes 0
perl 5.008
+ Test-Simple-1.302140
+ pathname: E/EX/EXODIST/Test-Simple-1.302140.tar.gz
+ provides:
+ Test2 1.302140
+ Test2::API 1.302140
+ Test2::API::Breakage 1.302140
+ Test2::API::Context 1.302140
+ Test2::API::Instance 1.302140
+ Test2::API::Stack 1.302140
+ Test2::Event 1.302140
+ Test2::Event::Bail 1.302140
+ Test2::Event::Diag 1.302140
+ Test2::Event::Encoding 1.302140
+ Test2::Event::Exception 1.302140
+ Test2::Event::Fail 1.302140
+ Test2::Event::Generic 1.302140
+ Test2::Event::Note 1.302140
+ Test2::Event::Ok 1.302140
+ Test2::Event::Pass 1.302140
+ Test2::Event::Plan 1.302140
+ Test2::Event::Skip 1.302140
+ Test2::Event::Subtest 1.302140
+ Test2::Event::TAP::Version 1.302140
+ Test2::Event::V2 1.302140
+ Test2::Event::Waiting 1.302140
+ Test2::EventFacet 1.302140
+ Test2::EventFacet::About 1.302140
+ Test2::EventFacet::Amnesty 1.302140
+ Test2::EventFacet::Assert 1.302140
+ Test2::EventFacet::Control 1.302140
+ Test2::EventFacet::Error 1.302140
+ Test2::EventFacet::Hub 1.302140
+ Test2::EventFacet::Info 1.302140
+ Test2::EventFacet::Meta 1.302140
+ Test2::EventFacet::Parent 1.302140
+ Test2::EventFacet::Plan 1.302140
+ Test2::EventFacet::Render 1.302140
+ Test2::EventFacet::Trace 1.302140
+ Test2::Formatter 1.302140
+ Test2::Formatter::TAP 1.302140
+ Test2::Hub 1.302140
+ Test2::Hub::Interceptor 1.302140
+ Test2::Hub::Interceptor::Terminator 1.302140
+ Test2::Hub::Subtest 1.302140
+ Test2::IPC 1.302140
+ Test2::IPC::Driver 1.302140
+ Test2::IPC::Driver::Files 1.302140
+ Test2::Tools::Tiny 1.302140
+ Test2::Util 1.302140
+ Test2::Util::ExternalMeta 1.302140
+ Test2::Util::Facets2Legacy 1.302140
+ Test2::Util::HashBase 1.302140
+ Test2::Util::Trace 1.302140
+ Test::Builder 1.302140
+ Test::Builder::Formatter 1.302140
+ Test::Builder::IO::Scalar 2.114
+ Test::Builder::Module 1.302140
+ Test::Builder::Tester 1.302140
+ Test::Builder::Tester::Color 1.302140
+ Test::Builder::Tester::Tie 1.302140
+ Test::Builder::TodoDiag 1.302140
+ Test::More 1.302140
+ Test::Simple 1.302140
+ Test::Tester 1.302140
+ Test::Tester::Capture 1.302140
+ Test::Tester::CaptureRunner 1.302140
+ Test::Tester::Delegate 1.302140
+ Test::use::ok 1.302140
+ ok 1.302140
+ requirements:
+ ExtUtils::MakeMaker 0
+ File::Spec 0
+ File::Temp 0
+ Scalar::Util 1.13
+ Storable 0
+ perl 5.006002
+ utf8 0
Test-TCP-1.21
pathname: T/TO/TOKUHIROM/Test-TCP-1.21.tar.gz
provides:
@@ -6606,22 +7138,30 @@ DISTRIBUTIONS
Test::More 0
Test::WWW::Mechanize 0
Try::Tiny 0
- Test-Warn-0.24
- pathname: C/CH/CHORNY/Test-Warn-0.24.tar.gz
+ Test-Warn-0.36
+ pathname: B/BI/BIGJ/Test-Warn-0.36.tar.gz
provides:
- Test::Warn 0.24
- Test::Warn::Categorization 0.24
- Test::Warn::DAG_Node_Tree 0.24
+ Test::Warn 0.36
requirements:
Carp 1.22
ExtUtils::MakeMaker 0
- File::Spec 0
Sub::Uplevel 0.12
Test::Builder 0.13
Test::Builder::Tester 1.02
- Test::More 0
- Tree::DAG_Node 1.02
perl 5.006
+ Test-Warnings-0.026
+ pathname: E/ET/ETHER/Test-Warnings-0.026.tar.gz
+ provides:
+ Test::Warnings 0.026
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ Test::Builder 0
+ parent 0
+ perl 5.006
+ strict 0
+ warnings 0
Test-use-ok-0.11
pathname: A/AU/AUDREYT/Test-use-ok-0.11.tar.gz
provides:
@@ -6746,6 +7286,17 @@ DISTRIBUTIONS
Test::More 0
Test::use::ok 0
Tie::RefHash 0
+ Time-Duration-Parse-0.14
+ pathname: N/NE/NEILB/Time-Duration-Parse-0.14.tar.gz
+ provides:
+ Time::Duration::Parse 0.14
+ requirements:
+ Carp 0
+ Exporter::Lite 0
+ ExtUtils::MakeMaker 0
+ perl 5.006
+ strict 0
+ warnings 0
TimeDate-2.30
pathname: G/GB/GBARR/TimeDate-2.30.tar.gz
provides:
@@ -7122,6 +7673,58 @@ DISTRIBUTIONS
Plack 0.9968
Syntax::Keyword::Gather 1.001
warnings::illegalproto 0.001
+ XML-LibXML-2.0132
+ pathname: S/SH/SHLOMIF/XML-LibXML-2.0132.tar.gz
+ provides:
+ XML::LibXML 2.0132
+ XML::LibXML::Attr 2.0132
+ XML::LibXML::AttributeHash 2.0132
+ XML::LibXML::Boolean 2.0132
+ XML::LibXML::CDATASection 2.0132
+ XML::LibXML::Comment 2.0132
+ XML::LibXML::Common 2.0132
+ XML::LibXML::Devel 2.0132
+ XML::LibXML::Document 2.0132
+ XML::LibXML::DocumentFragment 2.0132
+ XML::LibXML::Dtd 2.0132
+ XML::LibXML::Element 2.0132
+ XML::LibXML::ErrNo 2.0132
+ XML::LibXML::Error 2.0132
+ XML::LibXML::InputCallback 2.0132
+ XML::LibXML::Literal 2.0132
+ XML::LibXML::NamedNodeMap 2.0132
+ XML::LibXML::Namespace 2.0132
+ XML::LibXML::Node 2.0132
+ XML::LibXML::NodeList 2.0132
+ XML::LibXML::Number 2.0132
+ XML::LibXML::PI 2.0132
+ XML::LibXML::Pattern 2.0132
+ XML::LibXML::Reader 2.0132
+ XML::LibXML::RegExp 2.0132
+ XML::LibXML::RelaxNG 2.0132
+ XML::LibXML::SAX 2.0132
+ XML::LibXML::SAX::AttributeNode 2.0132
+ XML::LibXML::SAX::Builder 2.0132
+ XML::LibXML::SAX::Generator 2.0132
+ XML::LibXML::SAX::Parser 2.0132
+ XML::LibXML::Schema 2.0132
+ XML::LibXML::Text 2.0132
+ XML::LibXML::XPathContext 2.0132
+ XML::LibXML::XPathExpression 2.0132
+ XML::LibXML::_SAXParser 2.0132
+ requirements:
+ ExtUtils::MakeMaker 0
+ Test::More 0
+ XML::NamespaceSupport 1.07
+ XML::SAX 0.11
+ XML::SAX::Base 0
+ XML::SAX::Exception 0
+ base 0
+ parent 0
+ perl 5.008
+ strict 0
+ vars 0
+ warnings 0
XML-NamespaceSupport-1.11
pathname: P/PE/PERIGRIN/XML-NamespaceSupport-1.11.tar.gz
provides:
@@ -7341,6 +7944,13 @@ DISTRIBUTIONS
perl 5.008001
strict 0
warnings 0
+ boolean-0.46
+ pathname: I/IN/INGY/boolean-0.46.tar.gz
+ provides:
+ boolean 0.46
+ requirements:
+ ExtUtils::MakeMaker 0
+ perl 5.008001
gettext-1.05
pathname: P/PV/PVANDRY/gettext-1.05.tar.gz
provides:
diff --git a/docs/customising/config.md b/docs/customising/config.md
index 9f48bad7e..e0761cb8e 100644
--- a/docs/customising/config.md
+++ b/docs/customising/config.md
@@ -55,10 +55,23 @@ The following are all the configuration settings that you can change in `conf/ge
* <code><a href="#base_url">BASE_URL</a></code>
* <code><a href="#secure_proxy_ssl_header">SECURE_PROXY_SSL_HEADER</a></code>
-* <code><a href="#upload_dir">UPLOAD_DIR</a></code>
* <code><a href="#geo_cache">GEO_CACHE</a></code>
* <code><a href="#admin_base_url">ADMIN_BASE_URL</a></code>
+### Photo storage
+
+* <code><a href="#photo_storage_backend">PHOTO_STORAGE_BACKEND</a></code>
+* <code><a href="#photo_storage_options">PHOTO_STORAGE_OPTIONS</a></code>
+ * For local filesystem storage:
+ * <code><a href="#upload_dir">UPLOAD_DIR</a></code>
+ * For Amazon S3 storage:
+ * <code><a href="#bucket">BUCKET</a></code>
+ * <code><a href="#access_key">ACCESS_KEY</a></code>
+ * <code><a href="#secret_key">SECRET_KEY</a></code>
+ * <code><a href="#prefix">PREFIX</a></code>
+ * <code><a href="#create_bucket">CREATE_BUCKET</a></code>
+ * <code><a href="#region">REGION</a></code>
+
### Emailing
* <code><a href="#email_domain">EMAIL_DOMAIN</a></code>
@@ -404,18 +417,16 @@ LANGUAGES:
</dd>
<dt>
- <a name="upload_dir"><code>UPLOAD_DIR</code></a> &amp;
<a name="geo_cache"><code>GEO_CACHE</code></a>
</dt>
<dd>
- The file locations for uploaded photos and cached geocoding results.
- Normally you don't need to change these settings from the examples.
+ The file location for cached geocoding results.
+ Normally you don't need to change this setting from the example.
<div class="more-info">
<p>Example:</p>
<ul class="examples">
<li>
<code>
- UPLOAD_DIR: '../upload/'<br>
GEO_CACHE: '../cache/'
</code>
</li>
@@ -1118,5 +1129,179 @@ ALLOWED_COBRANDS:
</ul>
</div>
</dd>
-
+
+ <dt>
+ <a name="photo_storage_backend"><code>PHOTO_STORAGE_BACKEND</code></a>
+ </dt>
+ <dd>
+ The storage backend to use for uploaded photos.
+ <p>
+ Possible choices are <code>FileSystem</code> or <code>S3</code>.
+ By default, FixMyStreet will use <code>FileSystem</code>.
+ </p>
+ <p>
+ The chosen backend can be configured via the
+ <code><a href="#photo_storage_options">PHOTO_STORAGE_OPTIONS</a></code>
+ setting, see below.
+ </p>
+ </dd>
+
+ <dt>
+ <a name="photo_storage_options"><code>PHOTO_STORAGE_OPTIONS</code></a>
+ </dt>
+ <dd>
+ <p>
+ Contains backend-specific configuration options for photo storage.
+ </p>
+ <p>
+ For the <code>FileSystem</code> backend, the following apply:
+ </p>
+ <ul>
+ <li><code><a href="#upload_dir">UPLOAD_DIR</a></code></li>
+ </ul>
+ <p>
+ For the <code>S3</code> backend, the following apply:
+ </p>
+ <ul>
+ <li><code><a href="#bucket">BUCKET</a></code></li>
+ <li><code><a href="#access_key">ACCESS_KEY</a></code></li>
+ <li><code><a href="#secret_key">SECRET_KEY</a></code></li>
+ <li><code><a href="#prefix">PREFIX</a></code></li>
+ <li><code><a href="#create_bucket">CREATE_BUCKET</a></code></li>
+ <li><code><a href="#region">REGION</a></code></li>
+ </ul>
+ </dd>
+
+ <dt>
+ <a name="upload_dir"><code>UPLOAD_DIR</code></a>
+ </dt>
+ <dd>
+ <p>
+ The file location for uploaded photos.
+ Normally you don't need to change this setting from the example.
+ </p>
+ <p>
+ Only applies when <code>PHOTO_STORAGE_BACKEND</code> is <code>FileSystem</code>.
+ </p>
+ <div class="more-info">
+ <p>Example:</p>
+ <ul class="examples">
+ <li>
+ <pre>
+PHOTO_STORAGE_OPTIONS:
+ UPLOAD_DIR: '../upload/'
+ </pre>
+ </li>
+ </ul>
+ </div>
+ </dd>
+
+ <dt>
+ <a name="bucket"><code>BUCKET</code></a>
+ </dt>
+ <dd>
+ <p>
+ The name of the S3 bucket to store photos in.
+ </p>
+ <p>
+ <strong>Required</strong> when <code>PHOTO_STORAGE_BACKEND</code> is <code>S3</code>.
+ </p>
+ <div class="more-info">
+ <p>Example:</p>
+ <ul class="examples">
+ <li>
+ <pre>
+PHOTO_STORAGE_OPTIONS:
+ BUCKET: 'fixmystreet-photos'
+ </pre>
+ </li>
+ </ul>
+ </div>
+ </dd>
+
+ <dt>
+ <a name="access_key"><code>ACCESS_KEY</code></a> &amp;
+ <a name="secret_key"><code>SECRET_KEY</code></a>
+ </dt>
+ <dd>
+ <p>
+ The AWS access & secret keys to use when connecting to S3.
+ You should use a role with minimal privileges to manage objects in a specific S3 bucket, not your root keys.
+ </p>
+ <p>
+ <strong>Required</strong> when <code>PHOTO_STORAGE_BACKEND</code> is <code>S3</code>.
+ </p>
+ <div class="more-info">
+ <p>Example:</p>
+ <ul class="examples">
+ <li>
+ <pre>
+PHOTO_STORAGE_OPTIONS:
+ ACCESS_KEY: 'AKIAMYSUPERCOOLKEY'
+ SECRET_KEY: '12345/AbCdEFgHIJ98765'
+ </pre>
+ </li>
+ </ul>
+ </div>
+ </dd>
+
+ <dt>
+ <a name="prefix"><code>PREFIX</code></a>
+ </dt>
+ <dd>
+ <p>
+ An optional directory prefix to prepended to S3 filenames. Useful if, for example, you are using a bucket shared between other projects or FixMyStreet instances.
+ </p>
+ <p>
+ <strong>Optional</strong>. Only applies when <code>PHOTO_STORAGE_BACKEND</code> is <code>S3</code>.
+ </p>
+ <div class="more-info">
+ <p>Example:</p>
+ <ul class="examples">
+ <li>
+ <pre>
+PHOTO_STORAGE_OPTIONS:
+ PREFIX: '/fixmystreet_photos/'
+ </pre>
+ </li>
+ </ul>
+ </div>
+ </dd>
+
+ <dt>
+ <a name="create_bucket"><code>CREATE_BUCKET</code></a>
+ </dt>
+ <dd>
+ <p>
+ Set to <code>1</code> (or <code>true</code>) if FixMyStreet should create the S3 bucket specified in <code>BUCKET</code> if it doesn't already exist.
+ </p>
+ <p>
+ <strong>Optional</strong>. Only applies when <code>PHOTO_STORAGE_BACKEND</code> is <code>S3</code>.
+ </p>
+ </dd>
+
+ <dt>
+ <a name="region"><code>REGION</code></a>
+ </dt>
+ <dd>
+ <p>
+ The AWS region to create the S3 bucket in.
+ </p>
+ <p>
+ <strong>Optional</strong>. Only applies when <code>CREATE_BUCKET</code> is enabled.
+ </p>
+ <div class="more-info">
+ <p>Example:</p>
+ <ul class="examples">
+ <li>
+ <pre>
+PHOTO_STORAGE_OPTIONS:
+ CREATE_BUCKET: 1
+ REGION: 'eu-west-2'
+ </pre>
+ </li>
+ </ul>
+ </div>
+ </dd>
+
</dl>
diff --git a/docs/install/manual-install.md b/docs/install/manual-install.md
index 8acccdddf..84594f660 100644
--- a/docs/install/manual-install.md
+++ b/docs/install/manual-install.md
@@ -165,7 +165,7 @@ Some others you might want to look at, though the defaults are enough for it to
* [CONTACT_EMAIL]({{ "/customising/config/#contact_email" | relative_url }}) -- the email address to be used on the site for the contact us form.
* [DO_NOT_REPLY_EMAIL]({{ "/customising/config/#do_not_reply_email" | relative_url }}) -- the email address to be used on the site for e.g. confirmation emails.
* [STAGING_SITE]({{ "/customising/config/#staging_site" | relative_url }}) -- if this is 1 then all email (alerts and reports) will be sent to the contact email address. Use this for development sites.
-* [UPLOAD_DIR]({{ "/customising/config/#upload_dir" | relative_url }}) -- this is the location where images will be stored when they are uploaded. It should be accessible by and writeable by the FixMyStreet process.
+* [PHOTO_STORAGE_OPTIONS.UPLOAD_DIR]({{ "/customising/config/#upload_dir" | relative_url }}) -- this is the location where images will be stored when they are uploaded. It should be accessible by and writeable by the FixMyStreet process.
* [GEO_CACHE]({{ "/customising/config/#geo_cache" | relative_url }}) -- this is the location where Geolocation data will be cached. It should be accessible by and writeable by the FixMyStreet process.
If you are using Bing or Google maps you should also set one of
diff --git a/perllib/FixMyStreet.pm b/perllib/FixMyStreet.pm
index c8d22fe50..d10ce93aa 100644
--- a/perllib/FixMyStreet.pm
+++ b/perllib/FixMyStreet.pm
@@ -113,12 +113,14 @@ sub override_config($&) {
);
FixMyStreet::Map::reload_allowed_maps() if $config->{MAP_TYPE};
+ $FixMyStreet::PhotoStorage::instance = undef if $config->{PHOTO_STORAGE_BACKEND};
$code->();
$override_guard->restore();
mySociety::MaPit::configure() if $config->{MAPIT_URL};
FixMyStreet::Map::reload_allowed_maps() if $config->{MAP_TYPE};
+ $FixMyStreet::PhotoStorage::instance = undef if $config->{PHOTO_STORAGE_BACKEND};
}
=head2 dbic_connect_info
diff --git a/perllib/FixMyStreet/PhotoStorage/S3.pm b/perllib/FixMyStreet/PhotoStorage/S3.pm
new file mode 100644
index 000000000..45325e9dc
--- /dev/null
+++ b/perllib/FixMyStreet/PhotoStorage/S3.pm
@@ -0,0 +1,122 @@
+package FixMyStreet::PhotoStorage::S3;
+
+use Moose;
+use parent 'FixMyStreet::PhotoStorage';
+
+use Net::Amazon::S3;
+use Try::Tiny;
+
+
+has client => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $key = FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{ACCESS_KEY};
+ my $secret = FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{SECRET_KEY};
+
+ my $s3 = Net::Amazon::S3->new(
+ aws_access_key_id => $key,
+ aws_secret_access_key => $secret,
+ retry => 1,
+ );
+ return Net::Amazon::S3::Client->new( s3 => $s3 );
+ },
+);
+
+has bucket => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ shift->client->bucket( name => FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{BUCKET} );
+ },
+);
+
+has region => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ return FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{REGION};
+ },
+);
+
+has prefix => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ my $prefix = FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{PREFIX};
+ return "" unless $prefix;
+ $prefix =~ s#/$##;
+ return "$prefix/";
+ },
+);
+
+sub init {
+ my $self = shift;
+
+ return 1 if $self->_bucket_exists();
+
+ if ( FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{CREATE_BUCKET} ) {
+ my $name = $self->bucket->name;
+ try {
+ $self->client->create_bucket(
+ name => $name,
+ location_constraint => $self->region,
+ );
+ } catch {
+ warn "\x1b[31mCouldn't create S3 bucket '$name'\x1b[0m\n";
+ return;
+ };
+
+ return 1 if $self->_bucket_exists();
+
+ warn "\x1b[31mCouldn't create S3 bucket '$name'\x1b[0m\n";
+ return;
+ } else {
+ my $bucket = $self->bucket->name;
+ warn "\x1b[31mS3 bucket '$bucket' doesn't exist and CREATE_BUCKET is not set.\x1b[0m\n";
+ return;
+ }
+}
+
+sub _bucket_exists {
+ my $self = shift;
+ my $name = $self->bucket->name;
+ my @buckets = $self->client->buckets;
+ return grep { $_->name eq $name } @buckets;
+}
+
+sub get_object {
+ my ($self, $key) = @_;
+ return $self->bucket->object( key => $key );
+}
+
+sub store_photo {
+ my ($self, $photo_blob) = @_;
+
+ my $type = $self->detect_type($photo_blob) || 'jpeg';
+ my $fileid = $self->get_fileid($photo_blob);
+ my $key = $self->prefix . "$fileid.$type";
+
+ my $object = $self->get_object($key);
+ $object->put($photo_blob);
+
+ return $key;
+}
+
+
+sub retrieve_photo {
+ my ($self, $key) = @_;
+
+ my $object = $self->get_object($key);
+ if ($object->exists) {
+ my ($fileid, $type) = split /\./, $key;
+ return ($object->get, $type);
+ }
+
+}
+
+sub validate_key { $_[1] }
+
+
+1;
diff --git a/t/app/model/photoset.t b/t/app/model/photoset.t
index 708bda891..29a28d232 100644
--- a/t/app/model/photoset.t
+++ b/t/app/model/photoset.t
@@ -72,4 +72,29 @@ subtest 'Photoset with 3 referenced photo' => sub {
};
+subtest 'Correct storage backends are instantiated' => sub {
+ FixMyStreet::override_config {
+ PHOTO_STORAGE_BACKEND => 'FileSystem'
+ }, sub {
+ my $photoset = FixMyStreet::App::Model::PhotoSet->new;
+ isa_ok $photoset->storage, 'FixMyStreet::PhotoStorage::FileSystem';
+ };
+
+ FixMyStreet::override_config {
+ PHOTO_STORAGE_BACKEND => undef
+ }, sub {
+ my $photoset = FixMyStreet::App::Model::PhotoSet->new;
+ isa_ok $photoset->storage, 'FixMyStreet::PhotoStorage::FileSystem';
+ };
+
+ FixMyStreet::override_config {
+ PHOTO_STORAGE_BACKEND => 'S3'
+ }, sub {
+ my $photoset = FixMyStreet::App::Model::PhotoSet->new;
+ isa_ok $photoset->storage, 'FixMyStreet::PhotoStorage::S3';
+ };
+
+};
+
+
done_testing();
diff --git a/t/photostorage/s3.t b/t/photostorage/s3.t
new file mode 100644
index 000000000..fa989374f
--- /dev/null
+++ b/t/photostorage/s3.t
@@ -0,0 +1,164 @@
+#!/usr/bin/env perl
+use FixMyStreet::Test;
+
+use Test::MockModule;
+use Path::Tiny 'path';
+use Net::Amazon::S3::Client::Bucket;
+
+use_ok( 'FixMyStreet::PhotoStorage::S3' );
+
+FixMyStreet::override_config {
+ PHOTO_STORAGE_OPTIONS => {
+ ACCESS_KEY => 'AKIAMYFAKEACCESSKEY',
+ SECRET_KEY => '1234/fAk35eCrETkEy',
+ BUCKET => 'fms-test-photos',
+ PREFIX => '/uploads',
+ },
+}, sub {
+
+ my $s3 = FixMyStreet::PhotoStorage::S3->new();
+
+ subtest "basic attributes are configured correctly" => sub {
+ ok $s3->client, "N::A::S3::Client created";
+ is $s3->client->s3->aws_access_key_id, 'AKIAMYFAKEACCESSKEY', "Correct access key used";
+ is $s3->client->s3->aws_secret_access_key, '1234/fAk35eCrETkEy', "Correct secret key used";
+
+ ok $s3->bucket, "N::A::S3::Bucket created";
+ is $s3->bucket->name, 'fms-test-photos', "Correct bucket name configured";
+
+ is $s3->prefix, '/uploads/', "Correct key prefix with trailing slash";
+ };
+
+ subtest "photos can be stored in S3" => sub {
+ my $photo_blob = path('t/app/controller/sample.jpg')->slurp;
+ is $s3->get_fileid($photo_blob), '74e3362283b6ef0c48686fb0e161da4043bbcc97', "File ID calculated correctly";
+ is $s3->detect_type($photo_blob), 'jpeg', "File type calculated correctly";
+
+ my $s3_object = Test::MockModule->new('Net::Amazon::S3::Client::Object');
+ my $put_called = 0;
+ $s3_object->mock('put', sub {
+ my ($self, $photo) = @_;
+ is $self->key, '/uploads/74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg', 'Object created with correct key';
+ is $self->bucket->name, 'fms-test-photos', 'Object stored in correct bucket';
+ is $photo, $photo_blob, 'Correct photo uploaded';
+ $put_called = 1;
+ });
+
+ is $s3->store_photo($photo_blob), '/uploads/74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg', 'Photo uploaded and correct key returned';
+ ok $put_called, "Object::put called";
+ };
+
+ subtest "photos can be retrieved from S3" => sub {
+ my $photo_blob = path('t/app/controller/sample.jpg')->slurp;
+ my $key = '/uploads/74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg';
+
+ my $s3_object = Test::MockModule->new('Net::Amazon::S3::Client::Object');
+ my $exists_called = 0;
+ $s3_object->mock('exists', sub {
+ my ($self) = @_;
+ is $self->key, $key, 'Object::exists called with correct key';
+ $exists_called = 1;
+ return 1;
+ });
+ my $get_called = 0;
+ $s3_object->mock('get', sub {
+ my ($self) = @_;
+ is $self->key, $key, 'Object::get called with correct key';
+ is $self->bucket->name, 'fms-test-photos', 'Object fetched from correct bucket';
+ $get_called = 1;
+ return $photo_blob;
+ });
+
+ my ($photo, $type) = $s3->retrieve_photo($key);
+ ok $exists_called, "Object::exists called";
+ ok $get_called, "Object::get called";
+ is $photo, $photo_blob, 'Correct file content returned';
+ is $type, 'jpeg', 'Correct file type returned';
+ };
+
+ subtest "init passes if bucket exists" => sub {
+ my $s3_client = Test::MockModule->new('Net::Amazon::S3::Client');
+ my $buckets_called = 0;
+ $s3_client->mock('buckets', sub {
+ my $self = shift;
+ $buckets_called = 1;
+ return (
+ Net::Amazon::S3::Client::Bucket->new(
+ client => $self,
+ name => 'fms-test-photos'
+ )
+ );
+ });
+
+ ok $s3->init(), "PhotoStorage::S3::init succeeded";
+ ok $buckets_called, "Client::buckets called";
+ };
+
+ subtest "init fails if bucket doesn't exist" => sub {
+ my $s3_client = Test::MockModule->new('Net::Amazon::S3::Client');
+ my $buckets_called = 0;
+ $s3_client->mock('buckets', sub {
+ my $self = shift;
+ $buckets_called = 1;
+ return (
+ Net::Amazon::S3::Client::Bucket->new(
+ client => $self,
+ name => 'not-your-bucket'
+ )
+ );
+ });
+ my $create_bucket_called = 0;
+ $s3_client->mock('create_bucket', sub {
+ $create_bucket_called = 1;
+ });
+
+ ok !$s3->init(), "PhotoStorage::S3::init failed";
+ ok $buckets_called, "Client::buckets called";
+ ok !$create_bucket_called, "Client::create_bucket not called";
+ };
+};
+
+FixMyStreet::override_config {
+ PHOTO_STORAGE_OPTIONS => {
+ ACCESS_KEY => 'AKIAMYFAKEACCESSKEY',
+ SECRET_KEY => '1234/fAk35eCrETkEy',
+ BUCKET => 'fms-test-photos',
+ CREATE_BUCKET => 1,
+ REGION => 'eu-west-3',
+ },
+}, sub {
+
+ my $s3 = FixMyStreet::PhotoStorage::S3->new();
+
+ subtest "init creates bucket if CREATE_BUCKET set" => sub {
+ my $s3_client = Test::MockModule->new('Net::Amazon::S3::Client');
+ my $create_bucket_called = 0;
+ $s3_client->mock('create_bucket', sub {
+ my ( $self, %conf ) = @_;
+ $create_bucket_called = 1;
+ is $conf{name}, "fms-test-photos", "Bucket created with correct name";
+ is $conf{location_constraint}, "eu-west-3", "Bucket created in correct region";
+ });
+ my $buckets_called = 0;
+ $s3_client->mock('buckets', sub {
+ my $self = shift;
+ $buckets_called = 1;
+ return (
+ Net::Amazon::S3::Client::Bucket->new(
+ client => $self,
+ name => 'not-your-bucket'
+ ),
+ $create_bucket_called ? Net::Amazon::S3::Client::Bucket->new(
+ client => $self,
+ name => 'fms-test-photos'
+ ) : (),
+ );
+ });
+
+ ok $s3->init(), "PhotoStorage::S3::init succeeded";
+ ok $buckets_called, "Client::buckets called";
+ ok $create_bucket_called, "Client::create_bucket called";
+ };
+};
+
+done_testing();