Invoke FunctionGraph Function from FunctionGraph using temporary AK/SK¶
This page demonstrates how to call a FunctionGraph implemented in PHP from another FunctionGraph function using API calls and temporary security credentials (SecurityAccessKey/SecurityKey/SecurityToken) provided by an agency of Agency Type Cloud Service for Cloud Service FunctionGraph Service with permission to invoke FunctionGraph. for authentication.
Using temporary credentials the request has to be signed using the otc-api-sign-sdk-php.
See: Invoking FunctionGraph Event Function using API Calls for more details on how to use the REST API.
Prerequisites¶
URN of Function to be called. In this example the code of the function to be called is:
<?php function handler($event, $context) { $logger = $context->getLogger(); $logger->info("Function name: " . $context->getFunctionName()); return [ "statusCode" => 200, "isBase64Encoded" => false, "body" => json_encode($event), "headers" => [ "Content-Type" => "application/json" ] ]; }
Note
Ensure that the function and the subfunction are created in the same region.
An agency of Agency Type Cloud Service for Cloud Service FunctionGraph Service with permission to invoke FunctionGraph.
The permission policy should contain following policy statement:
{ "Version": "1.1", "Statement": [ { "Action": [ "functiongraph:function:invokeAsync*", "functiongraph:function:invoke" ], "Effect": "Allow" } ] }
or use an agency with default permission FunctionGraph CommonOperations.
Note
The permissions shown above are for demonstration purpose. Please follow the principle of least privilege when creating the permission policy for the agency.
e.g. to grant permission to invoke only specific functions, the policy statement should be like:
{ "Version": "1.1", "Statement": [ { "Action": [ "functiongraph:function:invokeAsync*", "functiongraph:function:invoke" ], "Effect": "Allow", "Resource": [ "RESOURCE_PATH" ] } ] }
where “RESOURCE_PATH” is in format
FunctionGraph:::function:group/function name
By adding Function name to the end of the generated prefix, you can define a specific path.
An asterisk * is allowed to indicate any function.
For example, FunctionGraph:*:*:function:default/* indicates any function in the default group.
For more details, see Policy Syntax in Identity and Access Management User Guide.
(Remark: changing the permission policy may take some time to take effect.)
Coding¶
Create a function with following content to call another FunctionGraph function:
<?php
// Sample code to invoke a FunctionGraph function from another FunctionGraph function
// using "AK/SK" from an agency and send it to the target FunctionGraph function
// defined as CALL_FG_URN in the user data.
// This example uses Guzzle for the HTTP request.
require __DIR__ . '/../vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request as HttpRequest;
use OTC\Request as OTCRequest;
use OTC\Signer;
function handler($event, $context)
{
$logger = $context->getLogger();
$callFgUrn = $context->getUserData('CALL_FG_URN');
$logger->info('Starting invocation of FunctionGraph function: ' . $callFgUrn);
$ak = $context->getSecurityAccessKey();
$sk = $context->getSecuritySecretKey();
$token = $context->getSecurityToken();
// get region from the function URN, default to 'eu-de' if not available
$region = $callFgUrn ? explode(':', $callFgUrn)[2] : 'eu-de';
$fgEndpoint = "https://functiongraph.{$region}.otc.t-systems.com";
// get Project ID from environment variable, default to empty string if not available
$projectId = getenv('RUNTIME_PROJECT_ID') ?: '';
$invokeUri = $fgEndpoint . '/v2/' . $projectId . '/fgs/functions/' . $callFgUrn . '/invocations';
$payload = json_encode([
'key' => 'Hello FunctionGraph',
], JSON_THROW_ON_ERROR);
$headers = [
'Content-Type' => 'application/json;charset=utf8',
'Host' => "functiongraph.{$region}.otc.t-systems.com",
'X-Project-Id' => $projectId,
];
$request = new OTCRequest('POST', $invokeUri, $headers, $payload);
$signer = new Signer();
$signer->Key = $ak;
$signer->Secret = $sk;
$signer->SecurityToken = $token;
$signer->Sign($request);
$client = new Client([
'verify' => false,
'timeout' => 30,
]);
$signedHeaders = [];
foreach ($signer->curlHeaders($request) as $headerValue) {
[$headerName, $headerContent] = explode(':', $headerValue, 2);
$signedHeaders[trim($headerName)] = trim($headerContent);
}
$httpRequest = new HttpRequest('POST', $invokeUri, $signedHeaders, $payload);
$response = $client->send($httpRequest);
$responseBody = (string) $response->getBody();
$logger->info('Response: ' . $responseBody);
$logger->info('Response status code: ' . $response->getStatusCode());
if ($response->getStatusCode() >= 400) {
throw new RuntimeException(
'Backend request failed with status ' . $response->getStatusCode() . ': ' . $responseBody
);
}
return $responseBody;
}
Create a composer.json file with following content:
{
"name": "opentelekomcloud-community/invoke-fg2fg-guzzle_AKSK",
"description": "Samples on how to invoke FunctionGraph from FunctionGraph functions using AK,SK and Guzzle",
"license": "Apache-2.0",
"type": "project",
"require": {
"opentelekomcloud-community/otc-api-sign-sdk-php": "1.0.2",
"guzzlehttp/guzzle": "^7.0"
},
"archive": {
"exclude": [
"Makefile"
]
},
"config": {
"archive-format": "zip",
"optimize-autoloader": true
}
}
Create a makefile with following content:
# create a zip package of the function code for deployment
# based on composer.json archive settings
create_package:
# install production dependencies without development dependencies
composer install --no-dev --prefer-dist
# create a code.zip archive of the production dependencies
composer archive --format=zip --file=code
# reinstall all dependencies including development dependencies
composer install
.PHONY: create_package
Create a function with following content to call another FunctionGraph function:
<?php
// Sample code to invoke a FunctionGraph function from another FunctionGraph function
// using "AK/SK" from an agency and send it to the target FunctionGraph function
// defined as CALL_FG_URN in the user data.
// This example uses native PHP cURL for the HTTP request.
require __DIR__ . '/../vendor/autoload.php';
use OTC\Request as OTCRequest;
use OTC\Signer;
function handler($event, $context)
{
$logger = $context->getLogger();
$callFgUrn = $context->getUserData('CALL_FG_URN');
$logger->info('Starting invocation of FunctionGraph function: ' . $callFgUrn);
$ak = $context->getSecurityAccessKey();
$sk = $context->getSecuritySecretKey();
$token = $context->getSecurityToken();
// get region from the function URN, default to 'eu-de' if not available
$region = $callFgUrn ? explode(':', $callFgUrn)[2] : 'eu-de';
$fgEndpoint = "https://functiongraph.{$region}.otc.t-systems.com";
// get Project ID from environment variable, default to empty string if not available
$projectId = getenv('RUNTIME_PROJECT_ID') ?: '';
$invokeUri = $fgEndpoint . '/v2/' . $projectId . '/fgs/functions/' . $callFgUrn . '/invocations';
$payload = json_encode([
'key' => 'Hello FunctionGraph',
], JSON_THROW_ON_ERROR);
$headers = [
'Content-Type' => 'application/json;charset=utf8',
'Host' => "functiongraph.{$region}.otc.t-systems.com",
'X-Project-Id' => $projectId,
];
$request = new OTCRequest('POST', $invokeUri, $headers, $payload);
$signer = new Signer();
$signer->Key = $ak;
$signer->Secret = $sk;
$signer->SecurityToken = $token;
$signer->Sign($request);
$curlHandle = null;
try {
$curlHandle = curl_init($invokeUri);
if ($curlHandle === false) {
throw new RuntimeException('Unable to initialize cURL');
}
curl_setopt_array($curlHandle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $signer->curlHeaders($request),
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 30,
]);
$responseBody = curl_exec($curlHandle);
if ($responseBody === false) {
throw new RuntimeException(curl_error($curlHandle));
}
$status = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
curl_close($curlHandle);
$curlHandle = null;
$logger->info('Response: ' . $responseBody);
$logger->info('Response status code: ' . $status);
if ($status >= 400) {
throw new RuntimeException(
'Backend request failed with status ' . $status . ': ' . $responseBody
);
}
return $responseBody;
} finally {
if ($curlHandle !== null) {
curl_close($curlHandle);
}
}
}
Create a composer.json file with following content:
{
"name": "opentelekomcloud-community/invoke-fg2fg-curl_AKSK",
"description": "Samples on how to invoke FunctionGraph from FunctionGraph functions using AK,SK and cURL",
"license": "Apache-2.0",
"type": "project",
"require": {
"opentelekomcloud-community/otc-api-sign-sdk-php": "1.0.2"
},
"archive": {
"exclude": [
"Makefile"
]
},
"config": {
"archive-format": "zip",
"optimize-autoloader": true
}
}
Create a makefile with following content:
# create a zip package of the function code for deployment
# based on composer.json archive settings
create_package:
# install production dependencies without development dependencies
composer install --no-dev --prefer-dist
# create a code.zip archive of the production dependencies
composer archive --format=zip --file=code
# reinstall all dependencies including development dependencies
composer install
.PHONY: create_package
Deployment¶
Create a deployment package using make create_package command and deploy the package to FunctionGraph using the console as an event function from scratch using PHP 8.3.
Configure the function:
set the handler name as src/index.handler.
specify an agency with permission to invoke FunctionGraph
and set the URN of the function to be called as environment variable with key CALL_FG_URN.
Testing¶
Create a test event based on Blank Template and click Test.
Execution Result on the right should show a successful execution and the function set in the CALL_FG_URN environment variable should have a new invoke request in its Monitoring.