Invoke FunctionGraph Function from FunctionGraph

This page demonstrates how to call a FunctionGraph implemented in Node.js 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.

See: Invoking FunctionGraph Event Function using API Calls for more details on how to use the REST API.

Prerequisites

  1. URN of Function to be called. In this example the code of the function to be called is:

    exports.handler = async (event, context) => {
      const output =
      {
          'statusCode': 200,
          'headers':
          {
              'Content-Type': 'application/json'
          },
          'isBase64Encoded': false,
          'body': JSON.stringify(event),
      }
      return output;
    }
    
  2. 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.

    (Remark: changing the permission policy may take some time to take effect.)

Coding

package.json

Create a package with following content for the FunctionGraph function that will call another FunctionGraph function.

{
  "name": "invoke-fg2fg",
  "version": "1.0.0",
  "private": true,
  "description": "Invoke FunctionGraph function from FunctionGraph function",
  "main": "index.js",
  "type": "commonjs",
  "engines": {
    "node": "20.15.1"
  },
  "scripts": {
    "postpack": "name=$(bash -c 'echo \"${npm_package_name////-}-${npm_package_version}\" | sed \"s/@/ /g\"') && rm -f ${name}.zip && tarball=\"${name}.tgz\"; tar -tf $tarball | sed 's/^package\\///' | zip -@r ${name}.zip; rm $tarball"
  },
  "license": "Apache-2.0",
  "dependencies": {
    "@opentelekomcloud-community/otc-api-sign-sdk-nodejs": "^1.0.0"
  },
  "bundleDependencies": [
    "@opentelekomcloud-community/otc-api-sign-sdk-nodejs"
  ],
  "files": [
    "*.js",
    "src/**/*",
    "lib/**/*"
  ]
}

index.js

Create a function with following content to call another FunctionGraph function:

"use strict";
const https = require("https");

const {
  Signer,
  HttpRequest,
} = require("@opentelekomcloud-community/otc-api-sign-sdk-nodejs");

exports.handler = async function (event, context) {
  // get temporary ak/sk/token from context
  // to use, an agency with permission to invoke FunctionGraph is needed:
  const ak = context.getSecurityAccessKey();
  const sk = context.getSecuritySecretKey();
  const token = context.getSecurityToken();

  // get the URN of the function to be called from user data
  const CALL_FG_URN = context.getUserData("CALL_FG_URN");

  // get region from function URN
  const region = CALL_FG_URN.split(":")[2] || "eu-de";

  // FunctionGraph endpoint
  const fgEndpoint = `https://functiongraph.${region}.otc.t-systems.com`;

  // get projectId from Runtime environment variable (set in FG backend)
  const projectId = process.env.RUNTIME_PROJECT_ID || "";

  // Endpoint for asynchronous invocation
  // const invokeURI = `${fgEndpoint}/v2/${projectId}/fgs/functions/${CALL_FG_URN}/invocations-async`;
  // or synchronous invocation
  const invokeURI = `${fgEndpoint}/v2/${projectId}/fgs/functions/${CALL_FG_URN}/invocations`;

  // set body according to your function input
  const body = {
    key: "Hello FunctionGraph",
  };
  const payload = JSON.stringify(body);

  // set headers
  const headers = {
    "Content-Type": "application/json;charset=utf8",
    Host: new URL(fgEndpoint).host,
    "X-Project-Id": projectId,
  };

  // create HttpRequest instance
  const request = new HttpRequest("POST", invokeURI, headers, payload);

  // create Signer instance and use temporary ak/sk/token to sign the request
  const signer = new Signer();
  signer.Key = ak;
  signer.Secret = sk;
  signer.SecurityToken = token;

  // sign the request
  const signedRequest = signer.Sign(request);

  // send the signed request
  return new Promise((resolve, reject) => {
    const req = https.request(signedRequest, (res) => {
      res.setEncoding("utf8");

      let responseBody = "";
      res.on("data", (chunk) => {
        responseBody += chunk;
      });

      res.on("end", () => {
        console.log("Response: ", responseBody);
        if (res.statusCode && res.statusCode >= 400) {
          reject(
            new Error(
              `Backend request failed with status ${res.statusCode}: ${responseBody}`,
            ),
          );
          return;
        }

        resolve(responseBody);
      });
    });

    req.on("error", reject);
    req.write(payload);
    req.end();
  });
};

Deployment

Create a deployment package using npm install and npm pack and deploy the package to FunctionGraph using the console as event function from scratch using Node.JS 20.15.

Configure the function:

  • set the handler name as 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.