4 * Amazon S3 file storage engine. This engine scales well but is relatively
5 * high-latency since data has to be pulled off S3.
7 * @task internal Internals
9 final class PhabricatorS3FileStorageEngine
10 extends PhabricatorFileStorageEngine
{
13 /* -( Engine Metadata )---------------------------------------------------- */
17 * This engine identifies as `amazon-s3`.
19 public function getEngineIdentifier() {
23 public function getEnginePriority() {
27 public function canWriteFiles() {
28 $bucket = PhabricatorEnv
::getEnvConfig('storage.s3.bucket');
29 $access_key = PhabricatorEnv
::getEnvConfig('amazon-s3.access-key');
30 $secret_key = PhabricatorEnv
::getEnvConfig('amazon-s3.secret-key');
31 $endpoint = PhabricatorEnv
::getEnvConfig('amazon-s3.endpoint');
32 $region = PhabricatorEnv
::getEnvConfig('amazon-s3.region');
34 return (strlen($bucket) &&
35 strlen($access_key) &&
36 strlen($secret_key) &&
42 /* -( Managing File Data )------------------------------------------------- */
46 * Writes file data into Amazon S3.
48 public function writeFile($data, array $params) {
49 $s3 = $this->newS3API();
51 // Generate a random name for this file. We add some directories to it
52 // (e.g. 'abcdef123456' becomes 'ab/cd/ef123456') to make large numbers of
53 // files more browsable with web/debugging tools like the S3 administration
55 $seed = Filesystem
::readRandomCharacters(20);
57 $parts[] = 'phabricator';
59 $instance_name = PhabricatorEnv
::getEnvConfig('cluster.instance');
60 if (strlen($instance_name)) {
61 $parts[] = $instance_name;
64 $parts[] = substr($seed, 0, 2);
65 $parts[] = substr($seed, 2, 2);
66 $parts[] = substr($seed, 4);
68 $name = implode('/', $parts);
70 AphrontWriteGuard
::willWrite();
71 $profiler = PhutilServiceProfiler
::getInstance();
72 $call_id = $profiler->beginServiceCall(
75 'method' => 'putObject',
79 ->setParametersForPutObject($name, $data)
82 $profiler->endServiceCall($call_id, array());
89 * Load a stored blob from Amazon S3.
91 public function readFile($handle) {
92 $s3 = $this->newS3API();
94 $profiler = PhutilServiceProfiler
::getInstance();
95 $call_id = $profiler->beginServiceCall(
98 'method' => 'getObject',
102 ->setParametersForGetObject($handle)
105 $profiler->endServiceCall($call_id, array());
112 * Delete a blob from Amazon S3.
114 public function deleteFile($handle) {
115 $s3 = $this->newS3API();
117 AphrontWriteGuard
::willWrite();
118 $profiler = PhutilServiceProfiler
::getInstance();
119 $call_id = $profiler->beginServiceCall(
122 'method' => 'deleteObject',
126 ->setParametersForDeleteObject($handle)
129 $profiler->endServiceCall($call_id, array());
133 /* -( Internals )---------------------------------------------------------- */
137 * Retrieve the S3 bucket name.
141 private function getBucketName() {
142 $bucket = PhabricatorEnv
::getEnvConfig('storage.s3.bucket');
144 throw new PhabricatorFileStorageConfigurationException(
146 "No '%s' specified!",
147 'storage.s3.bucket'));
153 * Create a new S3 API object.
157 private function newS3API() {
158 $access_key = PhabricatorEnv
::getEnvConfig('amazon-s3.access-key');
159 $secret_key = PhabricatorEnv
::getEnvConfig('amazon-s3.secret-key');
160 $region = PhabricatorEnv
::getEnvConfig('amazon-s3.region');
161 $endpoint = PhabricatorEnv
::getEnvConfig('amazon-s3.endpoint');
163 return id(new PhutilAWSS3Future())
164 ->setAccessKey($access_key)
165 ->setSecretKey(new PhutilOpaqueEnvelope($secret_key))
167 ->setEndpoint($endpoint)
168 ->setBucket($this->getBucketName());