Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

S3 resolver put options #535

Merged
merged 6 commits into from
Dec 10, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion DependencyInjection/Factory/Resolver/AwsS3ResolverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public function create(ContainerBuilder $container, $resolverName, array $config
$resolverDefinition->replaceArgument(0, new Reference($awsS3ClientId));
$resolverDefinition->replaceArgument(1, $config['bucket']);
$resolverDefinition->replaceArgument(2, $config['acl']);
$resolverDefinition->replaceArgument(3, $config['url_options']);
$resolverDefinition->replaceArgument(3, array_replace($config['url_options'], $config['get_options']));
$resolverDefinition->replaceArgument(4, $config['put_options']);
$resolverId = 'liip_imagine.cache.resolver.'.$resolverName;
$container->setDefinition($resolverId, $resolverDefinition);

Expand Down Expand Up @@ -89,10 +90,19 @@ public function addConfiguration(ArrayNodeDefinition $builder)
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
/** @deprecated Use `get_options` instead */
->arrayNode('url_options')
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
->arrayNode('get_options')
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
->arrayNode('put_options')
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
->arrayNode('proxies')
->defaultValue(array())
->prototype('scalar')->end()
Expand Down
78 changes: 65 additions & 13 deletions Imagine/Cache/Resolver/AwsS3Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ class AwsS3Resolver implements ResolverInterface
/**
* @var array
*/
protected $objUrlOptions;
protected $getOptions;

/**
* Object options added to PUT requests
*
* @var array
*/
protected $putOptions;

/**
* @var LoggerInterface
Expand All @@ -46,14 +53,16 @@ class AwsS3Resolver implements ResolverInterface
* @param S3Client $storage The Amazon S3 storage API. It's required to know authentication information.
* @param string $bucket The bucket name to operate on.
* @param string $acl The ACL to use when storing new objects. Default: owner read/write, public read
* @param array $objUrlOptions A list of options to be passed when retrieving the object url from Amazon S3.
* @param array $getOptions A list of options to be passed when retrieving the object url from Amazon S3.
* @param array $putOptions A list of options to be passed when saving the object to Amazon S3.
*/
public function __construct(S3Client $storage, $bucket, $acl = CannedAcl::PUBLIC_READ, array $objUrlOptions = array())
public function __construct(S3Client $storage, $bucket, $acl = CannedAcl::PUBLIC_READ, array $getOptions = array(), $putOptions = array())
{
$this->storage = $storage;
$this->bucket = $bucket;
$this->acl = $acl;
$this->objUrlOptions = $objUrlOptions;
$this->getOptions = $getOptions;
$this->putOptions = $putOptions;
}

/**
Expand Down Expand Up @@ -96,13 +105,18 @@ public function store(BinaryInterface $binary, $path, $filter)
$objectPath = $this->getObjectPath($path, $filter);

try {
$this->storage->putObject(array(
'ACL' => $this->acl,
'Bucket' => $this->bucket,
'Key' => $objectPath,
'Body' => $binary->getContent(),
'ContentType' => $binary->getMimeType()
));
$this->storage->putObject(
array_merge(
$this->putOptions,
array(
'ACL' => $this->acl,
'Bucket' => $this->bucket,
'Key' => $objectPath,
'Body' => $binary->getContent(),
'ContentType' => $binary->getMimeType(),
)
)
);
} catch (\Exception $e) {
$this->logError('The object could not be created on Amazon S3.', array(
'objectPath' => $objectPath,
Expand Down Expand Up @@ -176,10 +190,48 @@ public function remove(array $paths, array $filters)
* @param mixed $value The value to be set.
*
* @return AmazonS3Resolver $this
*
* @deprecated Use `setGetOption` instead
*/
public function setObjectUrlOption($key, $value)
{
$this->objUrlOptions[$key] = $value;
return $this->setGetOption($key, $value);
}

/**
* Sets a single option to be passed when retrieving an objects URL.
*
* If the option is already set, it will be overwritten.
*
* @see Aws\S3\S3Client::getObjectUrl() for available options.
*
* @param string $key The name of the option.
* @param mixed $value The value to be set.
*
* @return AmazonS3Resolver $this
*/
public function setGetOption($key, $value)
{
$this->getOptions[$key] = $value;

return $this;
}

/**
* Sets a single option to be passed when saving an object.
*
* If the option is already set, it will be overwritten.
*
* @see Aws\S3\S3Client::putObject() for available options.
*
* @param string $key The name of the option.
* @param mixed $value The value to be set.
*
* @return AmazonS3Resolver $this
*/
public function setPutOption($key, $value)
{
$this->putOptions[$key] = $value;

return $this;
}
Expand Down Expand Up @@ -210,7 +262,7 @@ protected function getObjectPath($path, $filter)
*/
protected function getObjectUrl($path)
{
return $this->storage->getObjectUrl($this->bucket, $path, 0, $this->objUrlOptions);
return $this->storage->getObjectUrl($this->bucket, $path, 0, $this->getOptions);
}

/**
Expand Down
57 changes: 53 additions & 4 deletions Resources/doc/cache-resolver/aws_s3.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ liip_imagine:
secret: %amazon.s3.secret%
region: %amazon.s3.region%
bucket: %amazon.s3.cache_bucket%
get_options:
Scheme: 'https'
put_options:
CacheControl: 'max-age=86400'
```

## Create resolver as a service
Expand Down Expand Up @@ -85,9 +89,9 @@ liip_imagine:

If enabled both first one will be [Cache](./cache.md), then [Proxy](./proxy.md) and after all process delegates to AwsS3 resolver.

## Object URL Options
## Object GET Options

In order to make use of the object URL options, you can simply add a call to the service, to alter those options you need.
In order to make use of the object GET options, you can simply add a call to the service, to alter those options you need.

``` yaml
services:
Expand All @@ -97,8 +101,8 @@ services:
- "@acme.amazon_s3"
- "%amazon_s3.bucket%"
calls:
# This calls $service->setObjectUrlOption('Scheme', 'https');
- [ setObjectUrlOption, [ 'Scheme', 'https' ] ]
# This calls $service->setGetOption('Scheme', 'https');
- [ setGetOption, [ 'Scheme', 'https' ] ]
tags:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```
Expand All @@ -118,5 +122,50 @@ services:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```

## Object PUT Options

Similar to Object GET Options you can configure additional options to be passed to S3 when storing objects.
This is useful, for example, to configure Cache-control headers returned when serving object from S3.
See [S3 SDK documentation](http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.S3.S3Client.html#_putObject) for the list of available options.

Note, that the following options are configured automatically and will be ignored, even if you configure it via ObjectOptions:
* `ACL`
* `Bucket`
* `Key`
* `Body`
* `ContentType`

In order to make use of the object PUT options, you can simply add a call to the service, to alter those options you need.

``` yaml
services:
acme.imagine.cache.resolver.amazon_s3:
class: Liip\ImagineBundle\Imagine\Cache\Resolver\AwsS3Resolver
arguments:
- "@acme.amazon_s3"
- "%amazon_s3.bucket%"
calls:
# This calls $service->setPutOption('CacheControl', 'max-age=86400');
- [ setPutOption, [ 'CacheControl', 'max-age=86400' ] ]
tags:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```

You can also use the constructor of the resolver to directly inject multiple options.

``` yaml
services:
acme.imagine.cache.resolver.amazon_s3:
class: Liip\ImagineBundle\Imagine\Cache\Resolver\AwsS3Resolver
arguments:
- "@acme.amazon_s3"
- "%amazon_s3.bucket%"
- "public-read" # Aws\S3\Enum\CannedAcl::PUBLIC_READ (default)
- { Scheme: https }
- { CacheControl: 'max-age=86400' }
tags:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```

- [Back to cache resolvers](../cache-resolvers.md)
- [Back to the index](../index.md)
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public function testCreateResolverDefinitionOnCreate()
'bucket' => 'theBucket',
'acl' => 'theAcl',
'url_options' => array('fooKey' => 'fooVal'),
'get_options' => array(),
'put_options' => array('barKey' => 'barVal'),
'cache' => false,
'proxies' => array()
));
Expand All @@ -57,6 +59,30 @@ public function testCreateResolverDefinitionOnCreate()
$this->assertEquals('theBucket', $resolverDefinition->getArgument(1));
$this->assertEquals('theAcl', $resolverDefinition->getArgument(2));
$this->assertEquals(array('fooKey' => 'fooVal'), $resolverDefinition->getArgument(3));
$this->assertEquals(array('barKey' => 'barVal'), $resolverDefinition->getArgument(4));
}

public function testOverrideDeprecatedUrlOptionsWithNewGetOptions()
{
$container = new ContainerBuilder;

$resolver = new AwsS3ResolverFactory;

$resolver->create($container, 'theResolverName', array(
'client_config' => array(),
'bucket' => 'theBucket',
'acl' => 'theAcl',
'url_options' => array('fooKey' => 'fooVal', 'barKey' => 'barVal'),
'get_options' => array('fooKey' => 'fooVal_overridden'),
'put_options' => array(),
'cache' => false,
'proxies' => array()
));

$this->assertTrue($container->hasDefinition('liip_imagine.cache.resolver.theresolvername'));

$resolverDefinition = $container->getDefinition('liip_imagine.cache.resolver.theresolvername');
$this->assertEquals(array('fooKey' => 'fooVal_overridden', 'barKey' => 'barVal'), $resolverDefinition->getArgument(3));
}

public function testCreateS3ClientDefinitionOnCreate()
Expand All @@ -70,6 +96,8 @@ public function testCreateS3ClientDefinitionOnCreate()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => false,
'proxies' => array()
));
Expand All @@ -94,6 +122,8 @@ public function testWrapResolverWithProxyOnCreateWithoutCache()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => false,
'proxies' => array('foo')
));
Expand Down Expand Up @@ -127,6 +157,8 @@ public function testWrapResolverWithCacheOnCreateWithoutProxy()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => 'theCacheServiceId',
'proxies' => array()
));
Expand Down Expand Up @@ -161,6 +193,8 @@ public function testWrapResolverWithProxyAndCacheOnCreate()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => 'theCacheServiceId',
'proxies' => array('foo')
));
Expand Down Expand Up @@ -203,6 +237,8 @@ public function testSetCachePrefixIfDefined()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache_prefix' => 'theCachePrefix',
'cache' => null,
'proxies' => array()
Expand Down Expand Up @@ -282,6 +318,14 @@ public function testProcessCorrectlyOptionsOnAddConfiguration()
'theKey' => 'theUrlOptionsVal',
'theOtherKey' => 'theOtherUrlOptionsValue'
);
$expectedGetOptions = array(
'theKey' => 'theGetOptionsVal',
'theOtherKey' => 'theOtherGetOptionsValue'
);
$expectedObjectOptions = array(
'theKey' => 'theObjectOptionsVal',
'theOtherKey' => 'theOtherObjectOptionsValue'
);
$expectedBucket = 'theBucket';
$expectedAcl = 'theAcl';
$expectedCachePrefix = 'theCachePrefix';
Expand All @@ -298,6 +342,8 @@ public function testProcessCorrectlyOptionsOnAddConfiguration()
'acl' => $expectedAcl,
'client_config' => $expectedClientConfig,
'url_options' => $expectedUrlOptions,
'get_options' => $expectedGetOptions,
'put_options' => $expectedObjectOptions,
'cache_prefix' => $expectedCachePrefix,
)
));
Expand All @@ -314,6 +360,12 @@ public function testProcessCorrectlyOptionsOnAddConfiguration()
$this->assertArrayHasKey('url_options', $config);
$this->assertEquals($expectedUrlOptions, $config['url_options']);

$this->assertArrayHasKey('get_options', $config);
$this->assertEquals($expectedGetOptions, $config['get_options']);

$this->assertArrayHasKey('put_options', $config);
$this->assertEquals($expectedObjectOptions, $config['put_options']);

$this->assertArrayHasKey('cache_prefix', $config);
$this->assertEquals($expectedCachePrefix, $config['cache_prefix']);
}
Expand Down Expand Up @@ -341,6 +393,9 @@ public function testAddDefaultOptionsIfNotSetOnAddConfiguration()
$this->assertArrayHasKey('url_options', $config);
$this->assertEquals(array(), $config['url_options']);

$this->assertArrayHasKey('get_options', $config);
$this->assertEquals(array(), $config['get_options']);

$this->assertArrayHasKey('cache_prefix', $config);
$this->assertNull($config['cache_prefix']);
}
Expand Down
23 changes: 23 additions & 0 deletions Tests/Imagine/Cache/Resolver/AwsS3ResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ public function testCreateObjectOnAmazon()
$this->assertNull($resolver->store($binary, 'thumb/foobar.jpg', 'thumb'));
}

public function testObjectOptionsPassedToS3ClintOnCreate()
{
$binary = new Binary('aContent', 'image/jpeg', 'jpeg');

$s3 = $this->getS3ClientMock();
$s3
->expects($this->once())
->method('putObject')
->with(array(
'CacheControl' => 'max-age=86400',
'ACL' => 'public-read',
'Bucket' => 'images.example.com',
'Key' => 'filter/images/foobar.jpg',
'Body' => 'aContent',
'ContentType' => 'image/jpeg',
))
;

$resolver = new AwsS3Resolver($s3, 'images.example.com');
$resolver->setPutOption('CacheControl', 'max-age=86400');
$resolver->store($binary, 'images/foobar.jpg', 'filter');
}

public function testIsStoredChecksObjectExistence()
{
$s3 = $this->getS3ClientMock();
Expand Down