Page MenuHomePhabricator

D14979.diff
No OneTemporary

D14979.diff

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.'));
+ }
+
+ $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

Mime Type
text/plain
Expires
Mon, May 13, 10:28 PM (2 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6277775
Default Alt Text
D14979.diff (14 KB)

Event Timeline