Cloud Function の関数タスク

Cloud Functions のタスクを使用すると、統合から Google Cloud Functions を構成して実行できます。Google Cloud Functions は軽量なコンピューティング ソリューションで、開発者はサーバーやランタイム環境を管理せずに、Cloud イベントに応答する単一目的のスタンドアロン関数を作成できます。

詳細については、Google Cloud Functions のドキュメントをご覧ください。

始める前に

Cloud Function タスクを使用する前に、Google Cloud プロジェクトで次のタスクを行う必要があります。

  1. Cloud Functions API(cloudfunctions.googleapis.com)を有効にします。

    Cloud Functions API の有効化

  2. プリンシパルに次の IAM ロールを割り当てます。
    • Cloud Functions 管理者(roles/cloudfunctions.admin
    • Application Integration 編集者(roles/integrations.integrationEditor
    • サービス アカウント ユーザー(roles/iam.serviceAccountUser

    プリンシパルにロールを付与する方法については、アクセス権の付与、変更、取り消しをご覧ください。

  3. Cloud Run 関数に接続するには、すでに OAuth 2.0 プロファイルが作成されているか、統合にユーザー管理のサービス アカウントが接続されていることを確認します。
    • 統合にサービス アカウントが接続されている場合は、そのサービス アカウントに [Cloud Functions の関数の起動元] の IAM ロールを割り当てます。

      サービス アカウントへのロールの付与については、サービス アカウントに対するアクセス権の管理をご覧ください。

    • Cloud Functions のタスクは、Google OIDC ID トークンタイプの認証プロファイルのみをサポートします。[Cloud Functions の関数の起動元] の IAM ロールが割り当てられたサービス アカウントを使用して、Google OIDC ID トークンタイプの認証プロファイルを作成します。Cloud Function タスクで認証が不要な場合、タスク構成ペインの [認証プロファイル] フィールドは空のままにしておくことができます。

    OIDC ID プロファイルとユーザー管理のサービス アカウントが両方とも構成されている場合、デフォルトでは OIDC ID プロファイルが認証に使用されます。OIDC ID プロファイルとユーザー管理のサービス アカウントがどちらも構成されていない場合は、デフォルトのサービス アカウント(service-PROJECT_NUMBER@gcp-sa-apigee.iam.gserviceaccount.com)を使用して Cloud Functions タスクが呼び出されます。

  4. Google Cloud プロジェクトで Apigee Integration 用に VPC Service Controls が設定されていないことを確認します。

Cloud Function タスクを構成する

統合で Cloud Function タスクを構成するには、次の手順に沿って操作します。

  1. Apigee UI で、Apigee 組織を選択します。
  2. [Develop] > [Integrations] の順にクリックします。
  3. 既存のインテグレーションを選択するか、[Create Integration] をクリックして新しいインテグレーションを作成します。

    新しいインテグレーションを作成する場合:

    1. [Create Integration] ダイアログで名前と説明を入力します。
    2. サポートされているリージョンのリストから、インテグレーションのリージョンを選択します。
    3. [Create] をクリックします。

    インテグレーション デザイナーでインテグレーションが開きます。

  4. インテグレーション デザイナーのナビゲーション バーで、[+ Add a task/trigger] > [Tasks] の順にクリックして、使用可能なタスクのリストを表示します。
  5. [Cloud Function] 要素をクリックしてインテグレーション エディタに配置します。
  6. デザイナーで [Cloud Function] 要素をクリックして構成ペインを開き、[Configure Cloud Function] をクリックします。
  7. [Cloud Function Configuration] ダイアログで、次のいずれかを選択します。
    • 既存の Function をリンクする。このオプションを選択すると、統合で構成されている既存の Cloud Function がリンクされます。Cloud Function の関数のトリガー URL を入力します。
    • 新しい Function を作成する。このオプションを選択して、統合に関連する新しい Cloud Function を作成します。Cloud Function 名を入力し、プルダウン リストから Function のリージョンを選択します。
  8. [Save] をクリックします。

    基本的な Google Cloud Function が Google Cloud プロジェクトに作成され、統合に関連付けられます。タスク構成ペインに、Cloud Function のトリガー URLタスク パラメータが表示されます。

Cloud Functions のテンプレート

既存の Cloud Functions の関数を使用して Cloud Functions の関数を構成する場合は、関数の main.pytask.pyrequirements.txt の各ソースファイルが次の形式になっていることを確認します。

task.py

  # Sample Code:
  # print(event.get('task_string_key'))
  # event.set('task_int_array_key', [456, 789]);
  # event.log('some logging')
  
  def run(event):
    """Actual cloud function custom logic.
    Args:
      event : event object in main.py that contains all parameters.
    """
    return     

main.py

  """Un-editable platform wrapper which invokes user code."""
import traceback

from flask import json
from flask import jsonify
from task import run

VALUE_NAME = [
    'stringValue', 'intValue', 'doubleValue', 'booleanValue', 'protoValue'
]
ARRAY_VALUE_NAME = {
    'stringArray': 'stringValues',
    'intArray': 'intValues',
    'doubleArray': 'doubleValues',
    'booleanArray': 'booleanValues',
    'protoArray': 'protoValues'
}
VALUE_TYPE_URL = 'type.googleapis.com/google.protobuf.Value'
CLOUD_FUNCTION_EXCEPTION_KEY = 'CloudFunctionException'
CLOUD_FUNCTION_LOGGING_KEY = 'CloudFunctionLogging'


class _Event(object):
  """Event object."""

  def __init__(self, json_payload):
    self._event_params = json_payload.get('eventParameters', dict())
    self._task_params = json_payload.get('taskParameters', dict())
    self._log = []
    print('Event param is ' + str(self._event_params))
    print('Task param is ' + str(self._task_params))

  def set(self, key, value):
    """Set the event parameters key-value.

    Args:
      key: parameter key.
      value: parameter value.
    """
    new_param = self._create_param(key, value)
    param = self._get_param_by_key(key)
    if param is None:
      if 'parameters' not in self._event_params:
        self._event_params['parameters'] = []
      self._event_params['parameters'].append(new_param)
    else:
      param['value'] = new_param['value']

  def _create_param(self, key, value):
    """Create a new parameter with given key value pair.

    Args:
      key: parameter key.
      value: parameter value.

    Returns:
      parameter.
    """
    new_param = {}
    new_param['key'] = key
    if isinstance(value, str):
      new_param['value'] = {'stringValue': value}
    elif isinstance(value, int):
      new_param['value'] = {'intValue': value}
    elif isinstance(value, float):
      new_param['value'] = {'doubleValue': value}
    elif isinstance(value, bool):
      new_param['value'] = {'booleanValue': value}
    elif isinstance(value, dict):
      if 'type@' in value:
        new_param['value'] = {'protoValue': value}
      else:
        new_param['value'] = {
            'protoValue': {
                '@type': 'type.googleapis.com/google.protobuf.Value',
                'value': value
            }
        }
    elif isinstance(value, list):
      if not value:
        raise RuntimeError('Cannot create a param with empty list')
      if any(not isinstance(val, type(value[0])) for val in value):
        print('Not all elements in the list have the same type')
        new_param['value'] = {
            'protoValue': {
                '@type': 'type.googleapis.com/google.protobuf.Value',
                'value': value
            }
        }
      elif isinstance(value[0], str):
        new_param['value'] = {'stringArray': {'stringValues': value}}
      elif isinstance(value[0], int):
        new_param['value'] = {'intArray': {'intValues': value}}
      elif isinstance(value[0], float):
        new_param['value'] = {'doubleArray': {'doubleValues': value}}
      elif isinstance(value[0], bool):
        new_param['value'] = {'booleanArray': {'booleanValues': value}}
      elif isinstance(value[0], dict):
        if all('@type' in val and val['@type'] == value[0]['@type']
               for val in value):
          new_param['value'] = {'protoArray': {'protoValues': value}}
        else:
          new_param['value'] = {
              'protoValue': {
                  '@type': 'type.googleapis.com/google.protobuf.Value',
                  'value': value
              }
          }
      else:
        raise RuntimeError('The type ' + type(value[0]) +
                           ' in the list is not supported')
    else:
      raise RuntimeError('Value ' + str(value) + ' has the type ' +
                         type(value) + ' that is not supported')
    return new_param

  def get(self, key):
    """Get the event parameter value for specified key.

    Args:
      key: parameter key.

    Returns:
      Parameter value.
    """
    param = self._get_param_by_key(key)
    if param is None:
      raise RuntimeError('Can not find param with key ' + key)
    return self._get_param_value(param)

  def _get_param_by_key(self, key):
    """Get the parameter for specified key.

    Args:
      key: parameter key.

    Returns:
      Parameter.
    """
    param = self._get_param_by_key_from_params(key, self._task_params)
    if param is None:
      return self._get_param_by_key_from_params(key, self._event_params)
    value = self._get_param_value(param)
    if isinstance(value, str) and len(value) > 2 and value.startswith(
        '$') and value.endswith('$'):
      return self._get_param_by_key_from_params(value[1:-1], self._event_params)
    return param

  def _get_param_by_key_from_params(self, key, params):
    """Get the parameter for specified key from event parameters.

    Args:
      key: parameter key.
      params: event parameters.

    Returns:
      Parameter.
    """
    if not isinstance(params, dict) or 'parameters' not in params:
      return None
    for param in params['parameters']:
      if param['key'] == key:
        return param
    return None

  def _get_param_value(self, param):
    """Get the parameter value for specified parameter.

    Args:
      param: parameter.

    Returns:
      Parameter value.
    """
    value = param['value']
    if len(value) != 1:
      raise RuntimeError('param does not have size of 1')
    for value_name in VALUE_NAME:
      if value_name in value:
        if value_name == 'protoValue' and value[value_name][
            '@type'] == VALUE_TYPE_URL:
          return value[value_name]['value']
        return value[value_name]
    for array_value_name in ARRAY_VALUE_NAME:
      if array_value_name in value:
        return value[array_value_name][ARRAY_VALUE_NAME[array_value_name]]
    raise RuntimeError('Cannot get value from param ' + str(param))

  def set_error(self):
    """Set the cloud function error to event parameters in order for user to see on IP."""

    self.set(CLOUD_FUNCTION_EXCEPTION_KEY, traceback.format_exc())

  def log(self, message):
    self._log.append(str(message))

  def get_response(self):
    """Get the response that can be returned to IP.

    Returns:
      The response text or any set of values that can be turned into a
      Response object using
      `make_response
      <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>`.
    """
    if self._log:
      self.set(CLOUD_FUNCTION_LOGGING_KEY, self._log)
    res = {
        'eventParameters': self._event_params,
    }
    return jsonify(**json.loads(json.htmlsafe_dumps(res)))


def execute_function(request):
  """Entry point of the cloud function.

  Args:
    request (flask.Request): HTTP request object.

  Returns:
    The response text or any set of values that can be turned into a
    Response object using
    `make_response
    <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>`.
  """
  try:
    request_json = request.get_json(silent=True)
    event = _Event(request_json)
    run(event)
  except:
    event.set_error()
  return event.get_response()

requirements.txt

# Function dependencies, for example:
# package>=version

Cloud Function タスクを編集する

Apigee Integration で Cloud Function タスクを構成すると、Google Cloud プロジェクトに HTTP でトリガーされる基本的な Cloud Function が作成されます。

Cloud Function タスクを編集するには、次の手順を行います。

  1. タスク構成ペインで、[Open Cloud Function] をクリックします。

    Google Cloud コンソールの [Function details] ページにリダイレクトされます。

  2. [Edit] をクリックします。
  3. [Configuration] ページでは、Cloud Functions のデフォルトの構成設定を編集できます。詳細については、Cloud Functions の構成をご覧ください。
  4. [Next] をクリックして、Cloud Function のソースコード ファイルを編集します。

    デフォルトでは、Cloud Function には次のソースファイルが含まれています。

    • main.py: このファイルには、統合から Cloud Function を実行する初期化コードが含まれています。
    • task.py: このファイルには、Cloud Function の実行コードが含まれています。run(event) 関数内でスクリプトを記述します。この関数は、Cloud Function タスクの実行時に呼び出されます。main.py ファイルの event オブジェクトには、すべてのタスク パラメータが含まれています。

      スクリプトの統合レベルで定義された変数を使用する方法については、統合変数へのアクセスをご覧ください。

  5. [Deploy] をクリックします。

統合変数にアクセスする

Cloud Function の統合変数にアクセスするには、タスク パラメータの形式で変数を Cloud Function のタスクに渡す必要があります。タスク パラメータは、Key-Value ペアです。Key は Cloud Function のソースファイルで使用される参照変数の名前で、Value は、参照変数がポイントする、対応する統合変数名です。タスク構成ペインの [Task Parameters] セクションで、1 つ以上のタスク パラメータを追加できます。

Cloud Functions の関数から統合変数にアクセスするには、次のメソッドを使用します。

  • set: 値を変数に書き込みます。
  • get: 変数の値を取得します。

たとえば、Cloud Function のソースファイルで使用する EmployeeName という名前の統合変数がある場合は、次のタスク パラメータを定義します。

  • キー: EmployeeKey
  • : EmployeeName

次のサンプル スクリプトは、set 関数と get 関数を使用して、定義された統合変数にアクセスします。

def run(event):
  # Read the integration variable EmployeeName using the reference variable EmployeeKey
  value = event.get('EmployeeKey');
  # Change the integration variable EmployeeName value using the reference variable EmployeeKey
  newValue = event.set('EmployeeKey' , 'XYZ');
  # The new value of the integration variable is retained throughout the Cloud Function task.
  return

エラー処理方法

タスクのエラー処理方法では、一時的なエラーによってタスクが失敗した場合のアクションを指定します。エラー処理方式と、さまざまな種類のエラー処理方式の詳細については、エラー処理方法をご覧ください。