# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 # # Permission is hereby granted, free of charge, to any person obtaining a copy of this # software and associated documentation files (the "Software"), to deal in the Software # without restriction, including without limitation the rights to use, copy, modify, # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AWSTemplateFormatVersion: 2010-09-09 Description: >- AWS CloudFormation template to create customizable PHP runtime in a Lambda Layer - Developed by Saud Albazei. Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Lambda Layer Configuration Parameters: - LambdaLayerName - PHPVersion - Architecture - Label: default: PHP Extensions (Leave to default for a minimal setup) Parameters: - PHPExtensionOpenSSL - PHPExtensioncURL - PHPExtensionJSON - PHPExtensionICONV - PHPExtensionLibXML - PHPExtensionFilter - PHPExtensionSimpleXML - PHPExtensionPhar - PHPExtensionRedis - PHPExtensionenBcmath - PHPExtensionCtype - PHPExtensionDom - PHPExtensionExif - PHPExtensionFileinfo - PHPExtensionFTP - PHPExtensionGettext - PHPExtensionMySQLi - PHPExtensionMySQLND - PHPExtensionOPCache - PHPExtensionpcntl - PHPExtensionPDO - PHPExtensionPDOSQLite - PHPExtensionPDOMySQL - PHPExtensionPosix - PHPExtensionReadline - PHPExtensionSession - PHPExtensionSOAP - PHPExtensionSockets - PHPExtensionSQLite3 - PHPExtensionTokenizer - PHPExtensionXML - PHPExtensionXMLReader - PHPExtensionXMLWriter - PHPExtensionZLib Parameters: LambdaLayerName: Description: Enter the Lambda Layer name Type: String Default: base-layer AllowedPattern: ^[a-zA-Z0-9-_]*$ ConstraintDescription: You must enter a valid layer name. Architecture: Description: Choose the instruction set architecture you want for your function code. Type: String Default: x86 AllowedValues: - x86 - ARM PHPVersion: Description: Choose the PHP version you would like to compile Type: String Default: php-8.0.11 AllowedValues: - php-8.0.11 - php-7.4.24 PHPExtensionOpenSSL: Description: Include OpenSSL (Required) Type: String Default: --with-openssl AllowedValues: - --with-openssl - --without-openssl PHPExtensioncURL: Description: Include cURL (Required) Type: String Default: --with-curl AllowedValues: - --with-curl - --without-curl PHPExtensionJSON: Description: Enable JSON (Required) Type: String Default: --enable-json AllowedValues: - --enable-json - --disable-json PHPExtensionICONV: Description: Include ICONV (Required for the AWS SDK) Type: String Default: --with-iconv AllowedValues: - --with-iconv - --without-iconv PHPExtensionLibXML: Description: Include LibXML (Required for the AWS SDK) Type: String Default: --with-libxml AllowedValues: - --with-libxml - --without-libxml PHPExtensionFilter: Description: Enable Filter (Required for the AWS SDK) Type: String Default: --enable-filter AllowedValues: - --enable-filter - --disable-filter PHPExtensionSimpleXML: Description: Enable SimpleXML (Required for the AWS SDK) Type: String Default: --enable-simplexml AllowedValues: - --enable-simplexml - --disable-simplexml PHPExtensionPhar: Description: Enable Phar Type: String Default: --disable-phar AllowedValues: - --enable-phar - --disable-phar PHPExtensionRedis: Description: Enable Redis Type: String Default: --disable-redis AllowedValues: - --enable-redis - --disable-redis PHPExtensionenBcmath: Description: Enable bcmath Type: String Default: --disable-bcmath AllowedValues: - --enable-bcmath - --disable-bcmath PHPExtensionCtype: Description: Enable ctype Type: String Default: --disable-ctype AllowedValues: - --enable-ctype - --disable-ctype PHPExtensionDom: Description: Enable dom Type: String Default: --disable-dom AllowedValues: - --enable-dom - --disable-dom PHPExtensionExif: Description: Enable exif Type: String Default: --disable-exif AllowedValues: - --enable-exif - --disable-exif PHPExtensionFileinfo: Description: Enable fileinfo Type: String Default: --disable-fileinfo AllowedValues: - --enable-fileinfo - --disable-fileinfo PHPExtensionFTP: Description: Enable FTP Type: String Default: --disable-ftp AllowedValues: - --enable-ftp - --disable-ftp PHPExtensionGettext: Description: With Gettext Type: String Default: --without-gettext AllowedValues: - --with-gettext - --without-gettext PHPExtensionMySQLi: Description: With MySQLi Type: String Default: --without-mysqli AllowedValues: - --with-mysqli - --without-mysqli PHPExtensionMySQLND: Description: Enable MySQLND Type: String Default: --disable-mysqlnd AllowedValues: - --enable-mysqlnd - --disable-mysqlnd PHPExtensionOPCache: Description: Enable OPCache Type: String Default: --disable-opcache AllowedValues: - --enable-opcache - --disable-opcache PHPExtensionpcntl: Description: Enable pcntl Type: String Default: --disable-pcntl AllowedValues: - --enable-pcntl - --disable-pcntl PHPExtensionPDO: Description: Enable PDO Type: String Default: --disable-pdo AllowedValues: - --enable-pdo - --disable-pdo PHPExtensionPDOSQLite: Description: With pdo_sqlite Type: String Default: --without-pdo_sqlite AllowedValues: - --with-pdo_sqlite - --without-pdo_sqlite PHPExtensionPDOMySQL: Description: With pdo_mysql Type: String Default: --without-pdo_mysql AllowedValues: - --with-pdo_mysql - --without-pdo_mysql PHPExtensionPosix: Description: Enable posix Type: String Default: --disable-posix AllowedValues: - --enable-posix - --disable-posix PHPExtensionReadline: Description: With readline Type: String Default: --without-readline AllowedValues: - --with-readline - --without-readline PHPExtensionSession: Description: Enable session Type: String Default: --disable-session AllowedValues: - --enable-session - --disable-session PHPExtensionSOAP: Description: Enable SOAP Type: String Default: --disable-soap AllowedValues: - --enable-soap - --disable-soap PHPExtensionSockets: Description: Enable Sockets Type: String Default: --disable-sockets AllowedValues: - --enable-sockets - --disable-sockets PHPExtensionSQLite3: Description: With SQLite3 Type: String Default: --without-sqlite3 AllowedValues: - --with-sqlite3 - --without-sqlite3 PHPExtensionTokenizer: Description: Enable Tokenizer Type: String Default: --disable-tokenizer AllowedValues: - --enable-tokenizer - --disable-tokenizer PHPExtensionXML: Description: Enable XML Type: String Default: --disable-xml AllowedValues: - --enable-xml - --disable-xml PHPExtensionXMLReader: Description: Enable XMLReader Type: String Default: --disable-xmlreader AllowedValues: - --enable-xmlreader - --disable-xmlreader PHPExtensionXMLWriter: Description: Enable XMLWriter Type: String Default: --disable-xmlwriter AllowedValues: - --enable-xmlwriter - --disable-xmlwriter PHPExtensionZLib: Description: With zlib Type: String Default: --without-zlib AllowedValues: - --with-zlib - --without-zlib Mappings: EC2Instance: x86: InstanceType: t3.small ARM: InstanceType: t4g.small AWSRegionAMI: af-south-1: x86: ami-03590d67411bbd24e ARM: RegionNotSupported eu-north-1: x86: ami-0f0b4cb72cf3eadf3 ARM: ami-039462d20158baacd ap-south-1: x86: ami-0a23ccb2cdd9286bb ARM: ami-080c1148a83cea662 eu-west-3: x86: ami-072056ff9d3689e7b ARM: ami-01815b727d927256f eu-west-2: x86: ami-0dbec48abfe298cab ARM: ami-000f10227a4a749fc eu-south-1: x86: ami-043c883e65558daa0 ARM: ami-011d768b31b27844d eu-west-1: x86: ami-0d1bf5b68307103c2 ARM: ami-0585cc9245d7565e4 ap-northeast-3: x86: ami-09ec82600a05bc23a ARM: RegionNotSupported ap-northeast-2: x86: ami-08c64544f5cfcddd0 ARM: ami-0f0005da8f9612974 me-south-1: x86: ami-0cc423da78c883d64 ARM: ami-0b61e3d1934f5996e ap-northeast-1: x86: ami-02892a4ea9bfa2192 ARM: ami-09c4848c1c6c4b09b sa-east-1: x86: ami-09b9b17384f68fd7c ARM: ami-001314db3c7a1274e ca-central-1: x86: ami-0e2407e55b9816758 ARM: ami-08e4cc0f2d564c300 ap-east-1: x86: ami-0de9d09f41c5e334c ARM: ami-035b0c1eedad4e78a ap-southeast-1: x86: ami-082105f875acab993 ARM: ami-0674db352b457179a ap-southeast-2: x86: ami-0210560cedcb09f07 ARM: ami-0e123196f540089e9 eu-central-1: x86: ami-07df274a488ca9195 ARM: ami-07d397b752b4016f8 us-east-1: x86: ami-087c17d1fe0178315 ARM: ami-029c64b3c205e6cce us-east-2: x86: ami-00dfe2c7ce89a450b ARM: ami-031dea1a744251b51 us-west-1: x86: ami-011996ff98de391d1 ARM: ami-0d455ad8eccc5556a us-west-2: x86: ami-0c2d06d50ce30b442 ARM: ami-0bfd41cde731af7cb Resources: IAMRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${AWS::StackName}-EC2-Role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: EC2AWSCLIAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - lambda:CreateFunction - lambda:PublishLayerVersion Resource: '*' Tags: - Key: Name Value: !Sub ${AWS::StackName}-EC2-Role IAMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref IAMRole InstanceProfileName: !Sub ${AWS::StackName}-EC2-Profile EC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: !FindInMap - EC2Instance - !Ref Architecture - InstanceType ImageId: !FindInMap - AWSRegionAMI - !Ref AWS::Region - !Ref Architecture IamInstanceProfile: !Ref IAMInstanceProfile Tags: - Key: Name Value: !Sub ${AWS::StackName}-PHP-Compiler-Instance UserData: Fn::Base64: !Sub | #!/bin/bash exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 yum update -y yum install autoconf bison gcc gcc-c++ libcurl-devel libxml2-devel openssl-devel libpng-devel libwebp-devel libjpeg-devel sqlite-devel oniguruma-devel readline-devel libxslt-devel -y curl -O https://www.php.net/distributions/${PHPVersion}.tar.gz tar -xvf ${PHPVersion}.tar.gz && cd ${PHPVersion} /${PHPVersion}/configure --disable-all --disable-cgi --enable-cli --disable-phpdbg --disable-phpdbg-webhelper --with-config-file-path=/var/task/conf.d --with-config-file-scan-dir=/var/task/conf.d ${PHPExtensionOpenSSL} ${PHPExtensioncURL} ${PHPExtensionICONV} ${PHPExtensionLibXML} ${PHPExtensionJSON} ${PHPExtensionPhar} ${PHPExtensionFilter} ${PHPExtensionSimpleXML} ${PHPExtensionRedis} ${PHPExtensionenBcmath} ${PHPExtensionCtype} ${PHPExtensionDom} ${PHPExtensionExif} ${PHPExtensionFileinfo} ${PHPExtensionFTP} ${PHPExtensionGettext} ${PHPExtensionMySQLi} ${PHPExtensionMySQLND} ${PHPExtensionOPCache} ${PHPExtensionpcntl} ${PHPExtensionPDO} ${PHPExtensionPDOSQLite} ${PHPExtensionPDOMySQL} ${PHPExtensionPosix} ${PHPExtensionReadline} ${PHPExtensionSession} ${PHPExtensionSOAP} ${PHPExtensionSockets} ${PHPExtensionSQLite3} ${PHPExtensionTokenizer} ${PHPExtensionXML} ${PHPExtensionXMLReader} ${PHPExtensionXMLWriter} ${PHPExtensionZLib} if [ $? != 0 ]; then /opt/aws/bin/cfn-signal -s 'false' --resource EC2Instance --stack ${AWS::StackName} --region ${AWS::Region} exit fi make -j2 if [ $? != 0 ]; then /opt/aws/bin/cfn-signal -s 'false' --resource EC2Instance --stack ${AWS::StackName} --region ${AWS::Region} exit fi mkdir /base-layer /base-layer/bin cp /${PHPVersion}/sapi/cli/php /base-layer/bin cat << 'EOF' > /base-layer/bootstrap #!/bin/sh set -euo pipefail while true; do /opt/bin/php "/opt/bootstrap.php" 2>&1; done EOF cat << 'EOF' > /base-layer/bootstrap.php getMessage()); } /* Simple function to handle HTTP requests using cURL. */ function http_request($type, $url, $payload = null) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true); if ($type == 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); } $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); curl_close($ch); return [ 'headers' => get_headers_from_curl_response($response), 'body' => substr($response, $header_size) ]; } /* Simple function to parse headers from the cURL response. */ function get_headers_from_curl_response($response) { $headers = array(); $header_text = substr($response, false, strpos($response, "\r\n\r\n")); foreach (explode("\r\n", $header_text) as $i => $line) if ($i === 0) $headers['http_code'] = $line; else { list($key, $value) = explode(': ', $line); $headers[$key] = $value; } return $headers; } /* Build the error response object. */ function error_response($error_message) { return [ 'statusCode' => 500, 'body' => $error_message ]; } /* Encode the response into a JSON object and send that response to the Lambda Runtime API. */ function send_response($invocation_id, $response) { $response_type = ($response['statusCode'] == 200) ? '/response' : '/error'; return http_request('POST', LAMBDA_RUNTIME_API_ENDPOINT . $invocation_id . $response_type, json_encode($response)); } /* Trigger the Lambda function. */ function trigger_handler($request) { if(!function_exists(LAMBDA_HANDLER_FUNCTION)){ return error_response("Handler function " . LAMBDA_HANDLER_FUNCTION . "() is not defined."); } try { return call_user_func(LAMBDA_HANDLER_FUNCTION, json_decode($request['body'])); } catch (Throwable $e) { return error_response($e->getMessage()); } } /* The main function that processes each event. */ function process_request() { $request = http_request('GET', LAMBDA_RUNTIME_API_ENDPOINT . 'next'); $response = $GLOBALS['init_error'] ?? trigger_handler($request); return send_response($request['headers']['Lambda-Runtime-Aws-Request-Id'], $response); } /* Infinite loop to get new requests and processes them and repeats. The Lambda Runtime API will keep the HTTP request on hold until it receives a new request then it returns a response to the waiting request immediately for processing */ while (true) { process_request(); /* It is highly recommended to carefully manage this infinite loop against memory leaks. The first option is to exit when current memory consumption is over 80%. The second one is set a finite number to the loop. if (memory_get_peak_usage(true) > $_ENV['AWS_LAMBDA_FUNCTION_MEMORY_SIZE']*1024*1024*0.8) exit("Execution environment ran out of memory"); if($current_loop >= $max_loops) exit("Number of loops exceeded the maximum allowed"); */ } EOF chmod 755 /base-layer/bootstrap cd /base-layer/ zip base-layer.zip bootstrap bootstrap.php bin/php aws lambda publish-layer-version --layer-name ${LambdaLayerName} --zip-file fileb://base-layer.zip --compatible-runtimes provided.al2 --region ${AWS::Region} if [ $? != 0 ]; then /opt/aws/bin/cfn-signal -s 'false' --resource EC2Instance --stack ${AWS::StackName} --region ${AWS::Region} exit fi /opt/aws/bin/cfn-signal -s 'true' --resource EC2Instance --stack ${AWS::StackName} --region ${AWS::Region} CreationPolicy: ResourceSignal: Timeout: PT30M Outputs: LambdaLayer: Description: The PHP layer has been published successfully. Feel free to delete this stack as it is no longer needed. Deleting this stack will not delete the created Lambda layer. Value: !Ref LambdaLayerName