Skip to content

Instantly share code, notes, and snippets.

@mnapoli
Last active December 9, 2018 21:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mnapoli/573e4f36a241e458fe9395b779f87511 to your computer and use it in GitHub Desktop.
Save mnapoli/573e4f36a241e458fe9395b779f87511 to your computer and use it in GitHub Desktop.
Bref

Clone https://github.com/mnapoli/bref-symfony-demo

Install aws sam CLI. Update the aws CLI console to the latest version too (I had to ditch the homebrew version and install via pip to get it).

Add the files found in this gist.

Run sam local start-api to test locally.

Create a S3 bucket, set it in the Makefile and run make deploy to deploy (sam validate can help finding errors in the config file).

#!/opt/bin/php -c/opt/php.ini
<?php
use Symfony\Component\Debug\Debug;
use Symfony\Component\Process\Process;
echo 'Cold start' . PHP_EOL;
error_reporting(E_ALL);
require __DIR__ . '/vendor/autoload.php';
Debug::enable();
while (true) {
$process = new Process(['/opt/bin/php', 'index.php']);
$process->setTimeout(null);
$process->run();
}
<?php
declare(strict_types=1);
use App\Kernel;
use Aws\CloudWatch\CloudWatchClient;
use Bref\Bridge\Psr7\RequestFactory;
use Bref\Bridge\Symfony\SymfonyAdapter;
use Bref\Http\LambdaResponse;
use Symfony\Component\Debug\Debug;
ini_set('display_errors', '1');
error_reporting(E_ALL);
$lambdaRuntimeApi = getenv('AWS_LAMBDA_RUNTIME_API');
require __DIR__ . '/vendor/autoload.php';
Debug::enable();
$kernel = new Kernel('prod', false);
$kernel->boot();
$symfonyAdapter = new SymfonyAdapter($kernel);
// This seems to be a blocking call
[$event, $invocationId] = waitForEvent($lambdaRuntimeApi);
if ($event === null) {
exit(0);
}
// Child process
$request = RequestFactory::fromLambdaEvent($event);
$response = $symfonyAdapter->handle($request);
$lambdaResponse = LambdaResponse::fromPsr7Response($response);
success($lambdaRuntimeApi, $invocationId, $lambdaResponse);
exit(0);
function waitForEvent(string $lambdaRuntimeApi): ?array
{
$ch = curl_init("http://$lambdaRuntimeApi/2018-06-01/runtime/invocation/next");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
$invocationId = '';
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$invocationId) {
if (! preg_match('/:\s*/', $header)) {
return strlen($header);
}
[$name, $value] = preg_split('/:\s*/', $header, 2);
if (strtolower($name) == 'lambda-runtime-aws-request-id') {
$invocationId = trim($value);
}
return strlen($header);
});
$body = '';
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$body) {
$body .= $chunk;
return strlen($chunk);
});
curl_exec($ch);
if (curl_error($ch)) {
die('Failed to fetch next Lambda invocation: ' . curl_error($ch) . "\n");
}
if ($invocationId == '') {
die('Failed to determine Lambda invocation ID');
}
curl_close($ch);
if (! $body) {
die("Empty Lambda invocation response\n");
}
$event = json_decode($body, true);
if (! array_key_exists('requestContext', $event)) {
fail($lambdaRuntimeApi, $invocationId, 'Event is not an API Gateway request');
return null;
}
return [$event, $invocationId];
}
function success(string $lambdaRuntimeApi, string $invocationId, LambdaResponse $response)
{
$ch = curl_init("http://$lambdaRuntimeApi/2018-06-01/runtime/invocation/$invocationId/response");
$response_json = $response->toJson();
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($response_json),
]);
curl_exec($ch);
curl_close($ch);
}
function fail($lambdaRuntimeApi, $invocationId, $errorMessage)
{
$ch = curl_init("http://$lambdaRuntimeApi/2018-06-01/runtime/invocation/$invocationId/response");
$response = [];
$response['statusCode'] = 500;
$response['body'] = $errorMessage;
$response_json = json_encode($response);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $response_json);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($response_json),
]);
curl_exec($ch);
curl_close($ch);
}
deploy: optimize package upload
optimize:
APP_ENV=prod php bin/console cache:clear --no-debug --no-warmup
APP_ENV=prod php bin/console cache:warmup
package:
sam package \
--template-file template.yaml \
--output-template-file .cloudformation.yaml \
--s3-bucket <CREATE A BUCKET MANUALLY AND PUT THE BUCKET NAME HERE>
upload:
sam deploy \
--template-file .cloudformation.yaml \
--stack-name bref-symfony-demo \
--capabilities CAPABILITY_IAM \
--region us-east-1
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'Bref Symfony demo'
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 10
MemorySize: 1024
Resources:
DemoFunction:
# See https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Type: AWS::Serverless::Function
Properties:
FunctionName: 'bref-symfony-demo'
Description: 'Bref Symfony demo'
CodeUri: .
Handler: bref.php
Runtime: provided
Layers:
- 'arn:aws:lambda:us-east-1:416566615250:layer:php-72:4'
Environment:
Variables:
APP_ENV: prod
APP_DEBUG: '0'
# This should not be committed but this is just a demo here, DO NOT DO THAT
APP_SECRET: '67d829bf61dc5f87a34fd814e2c9f629'
Events:
# See https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
HttpRoot:
Type: Api
Properties:
Path: /
Method: ANY
HttpSubPaths:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
Outputs:
DemoApi:
Description: "API Gateway endpoint URL for Prod stage"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
DemoFunction:
Description: "Demo Lambda Function ARN"
Value: !GetAtt DemoFunction.Arn
@nealio82
Copy link

nealio82 commented Dec 6, 2018

i'm getting a 500 response and Error: Runtime failed to start: fork/exec /var/task/bootstrap: permission denied Runtime.ExitError in my Cloudwatch logs, any idea where to start looking to debug?

@nealio82
Copy link

nealio82 commented Dec 6, 2018

maybe it's related to the error i get when i do sam local start-api and go to localhost:3000:

An error occurred (InvalidParameterValueException) when calling the GetLayerVersion operation: Invalid Layer name: arn:aws:lambda:us-east-1:416566615250:layer:php-72

@nealio82
Copy link

nealio82 commented Dec 6, 2018

I'm also now getting User root is not authorized to perform: lambda:GetLayerVersion on resource: arn:aws:lambda:us-east-1:416566615250:layer:php-72:4 (Service: AWSLambdaInternal; Status Code: 403; Error Code: AccessDeniedException; when I try to create a new stack - in that case I think I might need to create my own layer. Does this mean we'll have to create a PHP layer during deployment rather than sharing a 'supported' one?

@nealio82
Copy link

nealio82 commented Dec 7, 2018

just following up - after building my own PHP layer & updating template.yaml with my PHP ARN, I managed to replicate the permission denied error locally. chmod +x bootstrap solved it. I can now deploy a working example 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment