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

[SWP-5671] Support null AWS credentials for assumed IAM roles #18

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
18 changes: 5 additions & 13 deletions phpunit.xml → phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="vendor/autoload.php"
Expand All @@ -8,20 +10,10 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<testsuite name="Package Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
<exclude>
<directory>vendor</directory>
<directory>tests</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
2 changes: 2 additions & 0 deletions src/Contracts/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ interface Client
{
public function configure(array $channelConfig): Client;

public function getAwsConfig(): array;

public function putRecord(array $args = []): \Aws\Result;

public function putRecords(array $args = []): \Aws\Result;
Expand Down
10 changes: 10 additions & 0 deletions src/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,14 @@ public function handleBatch(array $records): void
// Fire and forget
}
}

/**
* Return the configured Client used to write records.
*
* @return Kinesis
*/
public function getClient(): Kinesis
{
return $this->client;
}
}
46 changes: 30 additions & 16 deletions src/Kinesis.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Kinesis implements Client
/** @var Repository */
protected $config;

/** @var array */
protected $awsConfig = [];

public function __construct(Repository $config)
{
$this->config = $config;
Expand All @@ -27,19 +30,6 @@ public function __construct(Repository $config)
* @return $this
*/
public function configure(array $channelConfig): Client
{
$this->kinesis = $this->configureKinesisClient($channelConfig);

return $this;
}

/**
* Configure the Kinesis client for logging records.
*
* @param array $channelConfig
* @return KinesisClient
*/
private function configureKinesisClient(array $channelConfig): KinesisClient
{
$defaultConfig = $this->config->get('services.kinesis');

Expand All @@ -49,13 +39,15 @@ private function configureKinesisClient(array $channelConfig): KinesisClient
'http' => Arr::get($channelConfig, 'http', Arr::get($defaultConfig, 'http', [])),
];

if (Arr::has($channelConfig, ['key', 'secret'])) {
if ($this->configHasCredentials($channelConfig)) {
$config['credentials'] = Arr::only($channelConfig, ['key', 'secret', 'token']);
} elseif (Arr::has($defaultConfig, ['key', 'secret'])) {
} elseif ($this->configHasCredentials($defaultConfig)) {
$config['credentials'] = Arr::only($defaultConfig, ['key', 'secret', 'token']);
}

return new KinesisClient($config);
$this->kinesis = new KinesisClient($this->awsConfig = $config);

return $this;
}

/**
Expand All @@ -79,4 +71,26 @@ public function putRecords(array $args = []): \Aws\Result
{
return $this->kinesis->putRecords($args);
}

/**
* Return the actual array used to configure the AWS Client.
*
* @return array
*/
public function getAwsConfig(): array
{
return $this->awsConfig;
}

/**
* Make sure some AWS credentials were provided to the configuration array.
*
* @return bool
*/
private function configHasCredentials(array $config): bool
{
return Arr::has($config, ['key', 'secret'])
&& is_string(Arr::get($config, 'key'))
&& is_string(Arr::get($config, 'secret'));
}
}
33 changes: 31 additions & 2 deletions tests/InteractsWithKinesis.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,49 @@
namespace PodPoint\MonologKinesis\Tests;

use Closure;
use Illuminate\Foundation\Application;
use PodPoint\MonologKinesis\Contracts\Client;

trait InteractsWithKinesis
{
public function mockKinesis(): \Mockery\MockInterface
protected function mockKinesis(): \Mockery\MockInterface
{
$mock = $this->mock(Client::class);

$mock->shouldReceive('configure')->andReturn($mock);

return $mock;
}

public function mockKinesisWith(Closure $mock): \Mockery\MockInterface
protected function mockKinesisWith(Closure $mock): \Mockery\MockInterface
{
return $this->mock(Client::class, $mock);
}

protected function withDefaultCredentials(Application $app): void
{
$app->config->set('services.kinesis.key', 'dummy-key');
$app->config->set('services.kinesis.secret', 'dummy-secret');
}

protected function withNullDefaultCredentials(Application $app): void
{
$app->config->set('services.kinesis.key', null);
$app->config->set('services.kinesis.secret', null);
}

protected function withoutDefaultCredentials(Application $app): void
{
// ...
}

protected function assertLoggerChannelAwsConfigEquals(string $channel, array $expected): void
{
$kinesisHandler = collect(logger()->channel($channel)->getHandlers())->first();

$this->assertInstanceOf(Handler::class, $kinesisHandler);

/** @var \PodPoint\MonologKinesis\Handler $kinesisHandler */
$this->assertEquals($expected, $kinesisHandler->getClient()->getAwsConfig());
}
}
110 changes: 84 additions & 26 deletions tests/MonologKinesisTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,81 @@ class MonologKinesisTest extends TestCase
{
use InteractsWithKinesis;

/** @define-env withDefaultCredentials */
public function test_aws_credentials_can_be_configured_at_the_service_level()
{
$kinesisHandler = collect(logger()->channel('some_channel')->getHandlers())->first();

$this->assertInstanceOf(\PodPoint\MonologKinesis\Handler::class, $kinesisHandler);

/** @var \PodPoint\MonologKinesis\Handler $kinesisHandler */
$this->assertEquals([
'region' => 'eu-west-1',
'version' => 'latest',
'http' => [],
'credentials' => [
'key' => 'dummy-key',
'secret' => 'dummy-secret',
],
], $kinesisHandler->getClient()->getAwsConfig());
}

/** @define-env withoutDefaultCredentials */
public function test_aws_credentials_can_be_completely_optional()
{
$kinesisHandler = collect(logger()->channel('some_channel')->getHandlers())->first();

$this->assertInstanceOf(\PodPoint\MonologKinesis\Handler::class, $kinesisHandler);

/** @var \PodPoint\MonologKinesis\Handler $kinesisHandler */
$this->assertEquals([
'region' => 'eu-west-1',
'version' => 'latest',
'http' => [],
], $kinesisHandler->getClient()->getAwsConfig());
}

/** @define-env withNullDefaultCredentials */
public function test_aws_credentials_can_be_null()
{
$kinesisHandler = collect(logger()->channel('some_channel')->getHandlers())->first();

$this->assertInstanceOf(\PodPoint\MonologKinesis\Handler::class, $kinesisHandler);

/** @var \PodPoint\MonologKinesis\Handler $kinesisHandler */
$this->assertEquals([
'region' => 'eu-west-1',
'version' => 'latest',
'http' => [],
], $kinesisHandler->getClient()->getAwsConfig());
}

/** @define-env withDefaultCredentials */
public function test_aws_credentials_can_be_configured_at_the_channel_level_overriding_default_credentials()
{
$this->mockKinesisWith(function ($mock) {
$mock->shouldReceive('configure')->once()->with(m::on(function ($argument) {
return $argument['key'] === 'some_other_key'
&& $argument['secret'] === 'some_other_secret'
&& $argument['region'] === 'another_region';
}))->andReturn($mock);

$mock->shouldReceive('putRecord')->once();
});

config()->set('logging.default', 'another_channel');
config()->set('logging.channels.another_channel', [
'driver' => 'kinesis',
'key' => 'some_other_key',
'secret' => 'some_other_secret',
'region' => 'another_region',
'stream' => 'logging',
'level' => 'debug',
]);

logger()->warning('Something went wrong.');
}

/**
* @return array
*/
Expand All @@ -26,7 +101,10 @@ public function loggerLevelTestProvider(): array
];
}

/** @dataProvider loggerLevelTestProvider $logLevel */
/**
* @dataProvider loggerLevelTestProvider
* @define-env withoutDefaultCredentials
*/
public function test_standard_log_levels_are_supported($logLevel)
{
$this->mockKinesis()
Expand All @@ -41,6 +119,7 @@ public function test_standard_log_levels_are_supported($logLevel)
logger()->$logLevel("Test {$logLevel} message");
}

/** @define-env withoutDefaultCredentials */
public function test_driver_does_not_log_below_default_log_level()
{
config()->set('logging.channels.some_channel.level', 'warning');
Expand All @@ -51,6 +130,7 @@ public function test_driver_does_not_log_below_default_log_level()
logger()->debug('Test debug message');
}

/** @define-env withoutDefaultCredentials */
public function test_driver_does_log_greater_than_or_equal_to_default_log_level()
{
config()->set('logging.channels.some_channel.level', 'warning');
Expand All @@ -61,6 +141,7 @@ public function test_driver_does_log_greater_than_or_equal_to_default_log_level(
logger()->error('Test error message');
}

/** @define-env withoutDefaultCredentials */
public function test_data_pushed_to_kinesis_is_properly_formatted()
{
$this->mockKinesis()->shouldReceive('putRecord')->once()->with(m::on(function ($argument) {
Expand All @@ -77,6 +158,7 @@ public function test_data_pushed_to_kinesis_is_properly_formatted()
logger()->info('Test info message');
}

/** @define-env withoutDefaultCredentials */
public function test_data_pushed_to_kinesis_can_also_forward_some_context()
{
$this->mockKinesis()->shouldReceive('putRecord')->once()->with(m::on(function ($argument) {
Expand All @@ -88,31 +170,7 @@ public function test_data_pushed_to_kinesis_can_also_forward_some_context()
logger()->info('Test info message', ['some_context' => ['key' => 'value']]);
}

public function test_channel_specific_aws_credentials_can_be_given()
{
$this->mockKinesisWith(function ($mock) {
$mock->shouldReceive('configure')->once()->with(m::on(function ($argument) {
return $argument['key'] === 'some_other_key'
&& $argument['secret'] === 'some_other_secret'
&& $argument['region'] === 'another_region';
}))->andReturn($mock);

$mock->shouldReceive('putRecord')->once();
});

config()->set('logging.default', 'another_channel');
config()->set('logging.channels.another_channel', [
'driver' => 'kinesis',
'key' => 'some_other_key',
'secret' => 'some_other_secret',
'region' => 'another_region',
'stream' => 'logging',
'level' => 'debug',
]);

logger()->warning('Something went wrong.');
}

/** @define-env withoutDefaultCredentials */
public function test_channel_specific_http_client_options_can_be_given()
{
$this->mockKinesisWith(function ($mock) {
Expand Down
10 changes: 3 additions & 7 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,11 @@ protected function getPackageProviders($app): array
*/
protected function defineEnvironment($app): void
{
$app['config']->set('services.kinesis', [
'key' => 'dummy-key',
'secret' => 'dummy-secret',
'region' => 'eu-west-1',
]);
$app->config->set('services.kinesis.region', 'eu-west-1');

$app['config']->set('logging.default', 'some_channel');
$app->config->set('logging.default', 'some_channel');

$app['config']->set('logging.channels.some_channel', [
$app->config->set('logging.channels.some_channel', [
'driver' => 'kinesis',
'stream' => 'logging',
'level' => 'debug',
Expand Down