Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15303662
D14979.id36187.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D14979.id36187.diff
View Options
diff --git a/bin/aws-s3 b/bin/aws-s3
new file mode 120000
--- /dev/null
+++ b/bin/aws-s3
@@ -0,0 +1 @@
+../scripts/utils/aws-s3.php
\ No newline at end of file
diff --git a/scripts/utils/aws-s3.php b/scripts/utils/aws-s3.php
new file mode 100755
--- /dev/null
+++ b/scripts/utils/aws-s3.php
@@ -0,0 +1,22 @@
+#!/usr/bin/env php
+<?php
+
+$root = dirname(dirname(dirname(__FILE__)));
+require_once $root.'/scripts/__init_script__.php';
+
+$args = new PhutilArgumentParser($argv);
+$args->setTagline(pht('AWS CLI Client for S3'));
+$args->setSynopsis(<<<EOSYNOPSIS
+**aws-s3** __command__ [__options__]
+ Upload and download data from Amazon Simple Storage Service (S3).
+
+EOSYNOPSIS
+ );
+$args->parseStandardArguments();
+
+$workflows = id(new PhutilClassMapQuery())
+ ->setAncestorClass('PhutilAWSS3ManagementWorkflow')
+ ->execute();
+
+$workflows[] = new PhutilHelpArgumentWorkflow();
+$args->parseWorkflows($workflows);
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -85,7 +85,10 @@
'PhutilAWSEC2Future' => 'future/aws/PhutilAWSEC2Future.php',
'PhutilAWSException' => 'future/aws/PhutilAWSException.php',
'PhutilAWSFuture' => 'future/aws/PhutilAWSFuture.php',
+ 'PhutilAWSManagementWorkflow' => 'future/aws/management/PhutilAWSManagementWorkflow.php',
'PhutilAWSS3Future' => 'future/aws/PhutilAWSS3Future.php',
+ 'PhutilAWSS3GetManagementWorkflow' => 'future/aws/management/PhutilAWSS3GetManagementWorkflow.php',
+ 'PhutilAWSS3ManagementWorkflow' => 'future/aws/management/PhutilAWSS3ManagementWorkflow.php',
'PhutilAWSv4Signature' => 'future/aws/PhutilAWSv4Signature.php',
'PhutilAWSv4SignatureTestCase' => 'future/aws/__tests__/PhutilAWSv4SignatureTestCase.php',
'PhutilAggregateException' => 'error/PhutilAggregateException.php',
@@ -603,7 +606,10 @@
'PhutilAWSEC2Future' => 'PhutilAWSFuture',
'PhutilAWSException' => 'Exception',
'PhutilAWSFuture' => 'FutureProxy',
+ 'PhutilAWSManagementWorkflow' => 'PhutilArgumentWorkflow',
'PhutilAWSS3Future' => 'PhutilAWSFuture',
+ 'PhutilAWSS3GetManagementWorkflow' => 'PhutilAWSS3ManagementWorkflow',
+ 'PhutilAWSS3ManagementWorkflow' => 'PhutilAWSManagementWorkflow',
'PhutilAWSv4Signature' => 'Phobject',
'PhutilAWSv4SignatureTestCase' => 'PhutilTestCase',
'PhutilAggregateException' => 'Exception',
diff --git a/src/future/aws/PhutilAWSFuture.php b/src/future/aws/PhutilAWSFuture.php
--- a/src/future/aws/PhutilAWSFuture.php
+++ b/src/future/aws/PhutilAWSFuture.php
@@ -3,11 +3,13 @@
abstract class PhutilAWSFuture extends FutureProxy {
private $future;
- private $awsAccessKey;
- private $awsPrivateKey;
- private $awsRegion;
- private $builtRequest;
- private $params;
+ private $accessKey;
+ private $secretKey;
+ private $region;
+ private $httpMethod = 'GET';
+ private $path = '/';
+ private $params = array();
+ private $endpoint;
abstract public function getServiceName();
@@ -15,73 +17,101 @@
parent::__construct(null);
}
- public function setAWSKeys($access, $private) {
- $this->awsAccessKey = $access;
- $this->awsPrivateKey = $private;
+ public function setAccessKey($access_key) {
+ $this->accessKey = $access_key;
return $this;
}
- public function getAWSAccessKey() {
- return $this->awsAccessKey;
+ public function getAccessKey() {
+ return $this->accessKey;
}
- public function getAWSPrivateKey() {
- return $this->awsPrivateKey;
+ public function setSecretKey(PhutilOpaqueEnvelope $secret_key) {
+ $this->secretKey = $secret_key;
+ return $this;
+ }
+
+ public function getSecretKey() {
+ return $this->secretKey;
}
- public function getAWSRegion() {
- return $this->awsRegion;
+ public function getRegion() {
+ return $this->region;
}
- public function setAWSRegion($region) {
- $this->awsRegion = $region;
+ public function setRegion($region) {
+ $this->region = $region;
return $this;
}
- public function getHost() {
- $host = $this->getServiceName().'.'.$this->awsRegion.'.amazonaws.com';
- return $host;
+ public function setEndpoint($endpoint) {
+ $this->endpoint = $endpoint;
+ return $this;
}
- public function setRawAWSQuery($action, array $params = array()) {
- $this->params = $params;
- $this->params['Action'] = $action;
+ public function getEndpoint() {
+ return $this->endpoint;
+ }
+
+ public function setHTTPMethod($method) {
+ $this->httpMethod = $method;
return $this;
}
- protected function getProxiedFuture() {
- if (!$this->future) {
- $params = $this->params;
+ public function getHTTPMethod() {
+ return $this->httpMethod;
+ }
- if (!$this->params) {
- throw new Exception(
- pht(
- 'You must %s!',
- 'setRawAWSQuery()'));
- }
+ public function setPath($path) {
+ $this->path = $path;
+ return $this;
+ }
- if (!$this->getAWSAccessKey()) {
- throw new Exception(
- pht(
- 'You must %s!',
- 'setAWSKeys()'));
- }
+ public function getPath() {
+ return $this->path;
+ }
- $params['AWSAccessKeyId'] = $this->getAWSAccessKey();
- $params['Version'] = '2013-10-15';
- $params['Timestamp'] = date('c');
+ protected function getParameters() {
+ $params = $this->params;
+ return $params;
+ }
- $params = $this->sign($params);
+ protected function getProxiedFuture() {
+ if (!$this->future) {
+ $params = $this->getParameters();
+ $method = $this->getHTTPMethod();
+ $host = $this->getEndpoint();
+ $path = $this->getPath();
- $uri = new PhutilURI('http://'.$this->getHost().'/');
- $uri->setQueryParams($params);
+ $uri = id(new PhutilURI("https://{$host}/"))
+ ->setPath($path)
+ ->setQueryParams($params);
- $this->future = new HTTPFuture($uri);
+ $future = id(new HTTPSFuture($uri))
+ ->setMethod($method);
+
+ $this->signRequest($future);
+
+ $this->future = $future;
}
return $this->future;
}
+ protected function signRequest(HTTPSFuture $future) {
+ $access_key = $this->getAccessKey();
+ $secret_key = $this->getSecretKey();
+
+ $region = $this->getRegion();
+
+ id(new PhutilAWSv4Signature())
+ ->setRegion($region)
+ ->setService($this->getServiceName())
+ ->setAccessKey($access_key)
+ ->setSecretKey($secret_key)
+ ->signRequest($future);
+ }
+
protected function didReceiveResult($result) {
list($status, $body, $headers) = $result;
@@ -101,7 +131,8 @@
);
if ($xml) {
$params['RequestID'] = $xml->RequestID[0];
- foreach ($xml->Errors[0] as $error) {
+ $errors = array($xml->Error);
+ foreach ($errors as $error) {
$params['Errors'][] = array($error->Code, $error->Message);
}
}
@@ -112,36 +143,4 @@
return $xml;
}
- /**
- * http://bit.ly/wU0JFh
- */
- private function sign(array $params) {
-
- $params['SignatureMethod'] = 'HmacSHA256';
- $params['SignatureVersion'] = '2';
-
- ksort($params);
-
- $pstr = array();
- foreach ($params as $key => $value) {
- $pstr[] = rawurlencode($key).'='.rawurlencode($value);
- }
- $pstr = implode('&', $pstr);
-
- $sign = "GET"."\n".
- strtolower($this->getHost())."\n".
- "/"."\n".
- $pstr;
-
- $hash = hash_hmac(
- 'sha256',
- $sign,
- $this->getAWSPrivateKey(),
- $raw_ouput = true);
-
- $params['Signature'] = base64_encode($hash);
-
- return $params;
- }
-
}
diff --git a/src/future/aws/PhutilAWSS3Future.php b/src/future/aws/PhutilAWSS3Future.php
--- a/src/future/aws/PhutilAWSS3Future.php
+++ b/src/future/aws/PhutilAWSS3Future.php
@@ -2,8 +2,42 @@
final class PhutilAWSS3Future extends PhutilAWSFuture {
+ private $bucket;
+
public function getServiceName() {
return 's3';
}
+ public function setBucket($bucket) {
+ $this->bucket = $bucket;
+ return $this;
+ }
+
+ public function getBucket() {
+ return $this->bucket;
+ }
+
+ public function setParametersForGetObject($key) {
+ $bucket = $this->getBucket();
+
+ $this->setHTTPMethod('GET');
+ $this->setPath($bucket.'/'.$key);
+
+ return $this;
+ }
+
+ protected function didReceiveResult($result) {
+ list($status, $body, $headers) = $result;
+
+ if (!$status->isError()) {
+ return $body;
+ }
+
+ if ($status->getStatusCode() === 404) {
+ return null;
+ }
+
+ return parent::didReceiveResult($result);
+ }
+
}
diff --git a/src/future/aws/PhutilAWSv4Signature.php b/src/future/aws/PhutilAWSv4Signature.php
--- a/src/future/aws/PhutilAWSv4Signature.php
+++ b/src/future/aws/PhutilAWSv4Signature.php
@@ -28,7 +28,7 @@
public function getDate() {
if ($this->date === null) {
- $this->date = date('c');
+ $this->date = gmdate('Ymd\THis\Z', time());
}
return $this->date;
}
diff --git a/src/future/aws/management/PhutilAWSManagementWorkflow.php b/src/future/aws/management/PhutilAWSManagementWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/future/aws/management/PhutilAWSManagementWorkflow.php
@@ -0,0 +1,69 @@
+<?php
+
+abstract class PhutilAWSManagementWorkflow
+ extends PhutilArgumentWorkflow {
+
+ public function isExecutable() {
+ return true;
+ }
+
+ protected function newAWSFuture($template) {
+ $argv = $this->getArgv();
+
+ $access_key = $argv->getArg('access-key');
+ $secret_key = $argv->getArg('secret-key');
+
+ $has_root = (strlen($access_key) || strlen($secret_key));
+ if ($has_root) {
+ if (!strlen($access_key) || !strlen($secret_key)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'When specifying AWS credentials with --access-key and '.
+ '--secret-key, you must provide both keys.'));
+ }
+
+ $template->setAccessKey($access_key);
+ $template->setSecretKey(new PhutilOpaqueEnvelope($secret_key));
+ }
+
+ $has_any = ($has_root);
+ if (!$has_any) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You must specify AWS credentials. Use --access-key and '.
+ '--secret-key to provide root credentials (discouraged).'));
+ }
+
+ $region = $argv->getArg('region');
+ if (!strlen($region)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You must specify an AWS region with --region.'));
+ }
+
+ $template->setRegion($region);
+
+ return $template;
+ }
+
+ protected function getAWSArguments() {
+ return array(
+ array(
+ 'name' => 'access-key',
+ 'param' => 'key',
+ 'help' => pht('AWS access key.'),
+ ),
+ array(
+ 'name' => 'secret-key',
+ 'param' => 'file',
+ 'help' => pht('AWS secret key.'),
+ ),
+ array(
+ 'name' => 'region',
+ 'param' => 'region',
+ 'help' => pht('AWS region.'),
+ ),
+ );
+ }
+
+}
diff --git a/src/future/aws/management/PhutilAWSS3GetManagementWorkflow.php b/src/future/aws/management/PhutilAWSS3GetManagementWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/future/aws/management/PhutilAWSS3GetManagementWorkflow.php
@@ -0,0 +1,57 @@
+<?php
+
+final class PhutilAWSS3GetManagementWorkflow
+ extends PhutilAWSS3ManagementWorkflow {
+
+ protected function didConstruct() {
+ $this
+ ->setName('get')
+ ->setExamples(
+ '**get** --key __key__')
+ ->setSynopsis(pht('Download content from S3.'))
+ ->setArguments(
+ array_merge(
+ $this->getAWSArguments(),
+ $this->getAWSS3BucketArguments(),
+ array(
+ array(
+ 'name' => 'key',
+ 'param' => 'key',
+ 'help' => pht('Specify a key to retrieve.'),
+ ),
+ )));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $bucket = $args->getArg('bucket');
+ if (!strlen($bucket)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify an AWS S3 bucket to access with --bucket.'));
+ }
+
+ $endpoint = $args->getArg('endpoint');
+ if (!strlen($endpoint)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify an AWS S3 endpoint with --endpoint.'));
+ }
+
+ $key = $args->getArg('key');
+ if (!strlen($key)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify an AWS S3 object key to access with --key.'));
+ }
+
+ $future = $this->newAWSFuture(new PhutilAWSS3Future())
+ ->setBucket($bucket)
+ ->setEndpoint($endpoint)
+ ->setParametersForGetObject($key);
+
+ echo $future->resolve();
+
+ return 0;
+ }
+
+}
diff --git a/src/future/aws/management/PhutilAWSS3ManagementWorkflow.php b/src/future/aws/management/PhutilAWSS3ManagementWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/future/aws/management/PhutilAWSS3ManagementWorkflow.php
@@ -0,0 +1,21 @@
+<?php
+
+abstract class PhutilAWSS3ManagementWorkflow
+ extends PhutilAWSManagementWorkflow {
+
+ protected function getAWSS3BucketArguments() {
+ return array(
+ array(
+ 'name' => 'bucket',
+ 'param' => 'bucket',
+ 'help' => pht('Name of the S3 bucket to access.'),
+ ),
+ array(
+ 'name' => 'endpoint',
+ 'param' => 'endpoint',
+ 'help' => pht('Name of the AWS region to access.'),
+ ),
+ );
+ }
+
+}
diff --git a/src/parser/argument/PhutilArgumentParser.php b/src/parser/argument/PhutilArgumentParser.php
--- a/src/parser/argument/PhutilArgumentParser.php
+++ b/src/parser/argument/PhutilArgumentParser.php
@@ -402,7 +402,9 @@
$this->parse($workflow->getArguments());
}
+
if ($workflow->isExecutable()) {
+ $workflow->setArgv($this);
$err = $workflow->execute($this);
exit($err);
} else {
diff --git a/src/parser/argument/workflow/PhutilArgumentWorkflow.php b/src/parser/argument/workflow/PhutilArgumentWorkflow.php
--- a/src/parser/argument/workflow/PhutilArgumentWorkflow.php
+++ b/src/parser/argument/workflow/PhutilArgumentWorkflow.php
@@ -80,6 +80,7 @@
private $specs = array();
private $examples;
private $help;
+ private $argv;
final public function __construct() {
$this->didConstruct();
@@ -154,6 +155,15 @@
return $this->specs;
}
+ final public function setArgv(PhutilArgumentParser $argv) {
+ $this->argv = $argv;
+ return $this;
+ }
+
+ final public function getArgv() {
+ return $this->argv;
+ }
+
protected function didConstruct() {
return null;
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Mar 7, 12:55 AM (1 w, 10 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7311076
Default Alt Text
D14979.id36187.diff (14 KB)
Attached To
Mode
D14979: Implement `bin/aws-s3 get ...` and a basic S3 client API
Attached
Detach File
Event Timeline
Log In to Comment