AWS LambdaからOCIリソースのAPI操作(Secrets Managerを使用)

## 概要
AWS Lambdaを使ってOCIリソースのAPI操作(Computeの開始操作)を行いました。
サーバレスにすることによってEventbridge等を使った時刻起動やイベント起動等でOCIリソース操作可能になります。
また、OCI資格情報をSecret Managerを使用することによって資格情報をコード外に保存しています。

EC2を使った操作はこちらを参照ください。
[AWS環境からOCIリソースのAPI操作(Secrets Managerを使用)](https://qiita.com/tktk2712/items/26217b46d767569212d9)

<font color="red">(トークンベース認証方法から通常のAPI認証に変更しました）</font>

## 構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/cf52fbe0-f463-4b04-d47d-3c176496890c.png)

## 設定手順

* OCI資格情報をSecrets Managerへ配置
以下の手順については[AWS環境からOCIリソースのAPI操作(Secrets Managerを使用)](https://qiita.com/tktk2712/items/26217b46d767569212d9)を参照下さい。
-OCI資格情報取得
-OCI Compute OCID
-Secrets Managerに資格情報を配置するための準備
-Secrets Managerに資格情報を配置

* Container Image作成
Lambdaは250MBまでのサイズ制限があり、OCIの外部モジュールだけで250MBを超えます。
そのため、EC2にてContainer Imageを作成、ECRにPushして使用します。
    *  EC2にSecret Managerアクセス権限追加
    操作するEC2にアタッチされたIAMロールに対してポリシー"SecretsManagerReadWrite"を追加します。この作業は必須ではありませんが、ECR登録前にContainer Imageのテストを行う場合は必要です。
    * EC2内に以下のディレクトリ構造でファイルを配置します。
```
work directory
├── Dockerfile_ocivmstart
└── app
    ├── app.py
    └── requirements.txt
```
```requirements.txt
boto3
oci
```

```app.py
import oci
import boto3
from botocore.exceptions import ClientError
import ast

# Secretmanager
session = boto3.session.Session()
client = session.client(
    service_name='secretsmanager',
    region_name='ap-northeast-1'
    )

def handler(event, context):
    # Resource Request
    instance_ocid = event['instance_ocid']
    instance_action = event['instance_action']

    #OCI config
    get_secret_value_response = client.get_secret_value(
      SecretId= 'OCIuser'
      )
    secret_data = get_secret_value_response['SecretString']
    secret = ast.literal_eval(secret_data)
    fingerprint = secret['fingerprint']
    oci_api_key = secret['oci_api_key']
    user = secret['user']
    tenancy = secret['tenancy']
    region = secret['region']

    pem_prefix = '-----BEGIN RSA PRIVATE KEY-----\n'
    pem_suffix = '\n-----END RSA PRIVATE KEY-----'
    key_content = '{}{}{}'.format(pem_prefix, oci_api_key, pem_suffix)

    config = {
        "user": user,
        "key_content": key_content,
        "fingerprint": fingerprint,
        "tenancy": tenancy,
        "region": region
        }

    oci.config.validate_config(config)

    # VM action
    core_client = oci.core.ComputeClient(config, service_endpoint='https://iaas.【リージョン】.oraclecloud.com')

    instance_action_response = core_client.instance_action(
        instance_id=instance_ocid,
        action=instance_action)
    print(instance_action_response.data)

```
参考
[Docs » API Reference » Core Services » ComputeClient](https://docs.oracle.com/en-us/iaas/tools/python/2.112.0/api/core/client/oci.core.ComputeClient.html)
[oci-python-sdk/examples/configuration_example.py](
https://github.com/oracle/oci-python-sdk/blob/master/examples/configuration_example.py)
``` Dockerfile_ocivmstart.
FROM public.ecr.aws/lambda/python:3.8

COPY app/requirements.txt ${LAMBDA_TASK_ROOT}
RUN pip install -r requirements.txt

COPY app/app.py ${LAMBDA_TASK_ROOT}

CMD [ "app.handler" ]
```
準備できたらイメージ作成と実行を行います。
```
docker build -t ocivmstart . -f Dockerfile_ocivmstart
docker run --rm -p 9000:8080 ocivmstart
```
確認コマンドを実行して、OCI Computeが起動できたら成功です
```
curl -d '{"instance_ocid":"【ComputeのOCID】","instance_action":"START"}' http://localhost:9000/2015-03-31/functions/function/invocations
```
参考：
[Lambda コンテナイメージをローカルでテストする](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-test.html)

* 作成したContainer ImageをECRにPush
AWSコンソールからAmazon>リポジトリでリポジトリを作成を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/3eed55a6-0815-a890-2cf9-b541a034093e.png)
リポジトリ名を入力してリポジトリを作成を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/ea7aaf31-a24d-d3e7-5102-18e140afcf9f.png)
作成後プッシュコマンドの表示を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/c14fd23e-e2ac-83c4-a15d-1c07f5ecbfd9.png)

表示されたコマンドをEC2で実施してContainer ImageをPush

Pushに成功すれば以下のようにイメージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/4b891a20-324d-9a49-c7fd-be13e08e8b66.png)

* Lambda作成
作成前にLambdaにアタッチするロールを作成します。
IAM>ロールの作成を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/f8ac1052-db79-5116-223f-d635da46794f.png)
以下のポリシーを含むロールを作成
AmazonEC2ContainerRegistryReadOnly
AWSLambdaBasicExecutionRole
SecretsManagerReadWrite
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/a811f06b-b420-1d5a-2090-195069ebda13.png)

Lambda>関数に移動し、関数の作成を押下
コンテナイメージを選択し、コンテナの場所を指定します。
また、実行ロールを先ほど作成したロールを指定して関数の作成を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/6e73dce4-421e-538b-d02a-e0d3b8078444.png)
作成完了しましたら基本設定の編集でタイムアウトを3秒から長い時間に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/f85e9f9c-b56c-27e7-ec0a-ea05a5d295ef.png)

## 実行結果
正常に実行できるかLambdaのテスト機能を使って確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/fe6d0188-e8cc-1ce2-f256-882cad96f2b9.png)
イベントJSONに以下を入力してテストボタンを押下します。
```
{
  "instance_ocid":"【ComputeのOCID】",
  "instance_action":"START"
}
```
参考:
[Python の Lambda 関数ハンドラー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-handler.html)

正常完了しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/20c193ea-b808-5e61-0d31-230030dd4d26.png)

OCIコンソールも確認して対象Computeが正常にスタートしたことを確認します。
Lambdaテスト実行前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/a4c5045b-513b-7d4e-22c0-e6a2747301c9.png)
Lambdaテスト実行後
Computeスタートしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/8e33b532-9b1f-88a6-0282-0ce9cb82c19a.png)

## サンプル2
OCIのVM以外にAutonomousDBやMySQL Database Serviceのリソースを開始/停止を行うサンプルです。

```
work directory
├── Dockerfile_ocivmstart
└── app
    ├── app.py
    └── requirements.txt
```
```requirements.txt
boto3
oci
```
```app.py
import oci
import boto3
from botocore.exceptions import ClientError
import ast

# Secretmanager
session = boto3.session.Session()
client = session.client(
    service_name='secretsmanager',
    region_name='ap-northeast-1'
    )

def handler(event, context):
    # Resource Request
    instance_type = event['instance_type']
    instance_ocid = event['instance_ocid']
    instance_action = event['instance_action']

    #OCI config
    get_secret_value_response = client.get_secret_value(
        SecretId= 'OCIuser'
        )
    secret_data = get_secret_value_response['SecretString']
    secret = ast.literal_eval(secret_data)
    fingerprint = secret['fingerprint']
    oci_api_key = secret['oci_api_key']
    user = secret['user']
    tenancy = secret['tenancy']
    region = secret['region']

    pem_prefix = '-----BEGIN RSA PRIVATE KEY-----\n'
    pem_suffix = '\n-----END RSA PRIVATE KEY-----'
    key_content = '{}{}{}'.format(pem_prefix, oci_api_key, pem_suffix)

    config = {
        "user": user,
        "key_content": key_content,
        "fingerprint": fingerprint,
        "tenancy": tenancy,
        "region": region
        }

    oci.config.validate_config(config)


    # VM action
    if instance_type == 'VM':
        core_client = oci.core.ComputeClient(config, service_endpoint='https://iaas.【リージョン】.oraclecloud.com')

        if instance_action == 'START':
            get_instance_response = core_client.get_instance(instance_ocid)

            if get_instance_response.data.lifecycle_state == 'STOPPED':
                instance_action_response = core_client.instance_action(
                    instance_id=instance_ocid,
                    action=instance_action)
                print(instance_action_response.data)

            else:
                print('VM START aborted')

        elif instance_action == 'STOP':
            get_instance_response = core_client.get_instance(instance_ocid)

            if get_instance_response.data.lifecycle_state == 'RUNNING':
                instance_action_response = core_client.instance_action(
                    instance_id=instance_ocid,
                    action=instance_action)
                print(instance_action_response.data)

            else:
                print('VM STOP aborted')

    # ADB action
    elif instance_type == 'ADB':
        database_client = oci.database.DatabaseClient(config, service_endpoint='https://database.【リージョン】.oraclecloud.com')

        if instance_action == 'START':
            get_autonomous_database_response = database_client.get_autonomous_database(
                autonomous_database_id=instance_ocid)

            if get_autonomous_database_response.data.lifecycle_state == 'STOPPED':
                start_autonomous_database_response = database_client.start_autonomous_database(
                    autonomous_database_id=instance_ocid)
                print(start_autonomous_database_response.data)

            else:
                print('ADB START aborted')

        elif instance_action == 'STOP':
            get_autonomous_database_response = database_client.get_autonomous_database(
                autonomous_database_id=instance_ocid)

            if get_autonomous_database_response.data.lifecycle_state == 'AVAILABLE':
                stop_autonomous_database_response = database_client.stop_autonomous_database(
                    autonomous_database_id=instance_ocid)
                print(stop_autonomous_database_response.data)

            else:
                print('ADB STOP aborted')

        else:
            pass

    # MDS action
    elif instance_type == 'MDS':
        mysql_client = oci.mysql.DbSystemClient(config, service_endpoint='https://mysql.【リージョン】.ocp.oraclecloud.com')

        if instance_action == 'START':
            get_db_system_response = mysql_client.get_db_system(
                db_system_id=instance_ocid)
            if get_db_system_response.data.lifecycle_state == 'INACTIVE':
                start_db_system_response = mysql_client.start_db_system(
                    db_system_id=instance_ocid)
                print(start_db_system_response.headers)

            else:
                print('MDS START aborted')

        elif instance_action == 'STOP':
            get_db_system_response = mysql_client.get_db_system(
                db_system_id=instance_ocid)
            if get_db_system_response.data.lifecycle_state == 'ACTIVE':
                stop_db_system_response = mysql_client.stop_db_system(
                    db_system_id=instance_ocid,
                    stop_db_system_details=oci.mysql.models.StopDbSystemDetails(
                        shutdown_type="IMMEDIATE")
                )
                print(stop_db_system_response.headers)

            else:
                print('MDS STOP aborted')

        else:
            pass

    # Unexpected Resource action
    else:
        pass
```
イベントJSONは以下のとおりとなります。
- instance_type : VM/ADB/MDS
- instance_ocid : リソースのOCID
- instance_action : START/STOP

ADBを開始する場合
```
{
    "instance_type":"ADB",
    "instance_ocid":"【OCID】",
    "instance_action":"START"
}
```