AWS lambda でgoogle spread sheetを操作したくなったのでやってみた [その2]

## 概要
前回の記事の続きです。
前回は下半分を作成したので今回は上半分を作成していきます

![Untitled (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344266/49ee362c-b673-10e9-130b-a6cde4288473.png)


## serverless framework 環境

### 動作環境
   npm (6.14.8) : 古くなければ...
   serverless (2.8.0) : 2.x.x系であれば
   python (3.8.2) :3.8系であれば

## 構成

以下、serverless frameworkを知っている方、pythonがなんとなくわかる方向けに
説明は省いております。ご参考程度にご覧ください。

```
functions/layers/serverless.yml　　# 構成ファイル
functions/layers/package.json　　　# パッケージ関連
functions/layers/requirements.txt # パッケージ関連
functions/layers/python/util.py　　# 共通関数

functions/main/serverless.yml　　# 構成ファイル
functions/main/handler.py        # lambda のメイン
```

パッケージが多くなったらlayersを作っておくのが便利です。
[参照 Aws Lambda レイヤー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html)

### 各モジュールの説明


functions/layers/serverless.yml

```yaml
service: goole-test-layer
frameworkVersion: "2"

plugins:
  - serverless-python-requirements

custom:
  defaultStage: dev
  pythonRequirements:
    dockerizePip: true
    layer: true

provider:
  name: aws
  runtime: python3.8
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  environment:
    TZ: Asia/Tokyo

package:
  exclude:
    - ./node_modules/** # パケージのある場所を定義

layers:
  LayersCommon:
    path: "./"  #pythonというフォルダに入れておくと共通関数としてlambda側から呼び出し可能になります
    compatibleRuntimes:
      - python3.8

resources:
  Outputs:
    PythonRequirementsLambdaLayerExport:
      Value:
        Ref: PythonRequirementsLambdaLayer ## function側の設定で使います
    LayersCommonLambdaLayerExport:
      Value:
        Ref: LayersCommonLambdaLayer ## function側の設定で使います
```

functions/layers/package.json

```text
{
  "name": "sample",
  "description": "",
  "version": "0.1.0",
  "dependencies": {},
  "devDependencies": {
    "serverless-python-requirements": "^5.1.0"
  }
}
```


functions/layers/requirements.txt

```text
boto3
botocore
gspread
oauth2client
```



functions/main/serverless.yml

```yaml

service: goole-test
frameworkVersion: "2"

custom:
  defaultStage: dev
  sampleS3BucketName:
    Fn::Join:
      - ""
      - - ${self:service}-
        - ${self:provider.stage}-
        - Ref: AWS::AccountId
  ## layerの設定[packege]
  requirements_service: goole-test-layer
  requirements_export: PythonRequirementsLambdaLayerExport
  requirements_layer: ${cf:${self:custom.requirements_service}-${self:provider.stage}.${self:custom.requirements_export}}
  ## layerの設定[common]
  layers_common_service: goole-test-layer
  layers_common_export: LayersCommonLambdaLayerExport
  layers_common: ${cf:${self:custom.layers_common_service}-${self:provider.stage}.${self:custom.layers_common_export}}

provider:
  name: aws
  runtime: python3.8
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  logRetentionInDays: 30
  environment:
    KEYNAME : "/google/access_key" # 作成したkeyの保管場所
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:ListBucket"
        - "s3:GetObject"
        - "s3:PutObject"
      Resource:
        - Fn::Join: ["", ["arn:aws:s3:::", { "Ref": "S3Bucket" }]]
        - Fn::Join: ["", ["arn:aws:s3:::", { "Ref": "S3Bucket" }, "/*"]]
    - Effect: Allow
      Action:
        - secretsmanager:GetSecretValue
      Resource:
        - "*" # secretsmanagerのarnを指定すればより権限制御が可能

functions:
  google_test:
    handler: handler.google_test
    memorySize: 512
    timeout: 900
    layers:
      - ${self:custom.requirements_layer}
      - ${self:custom.layers_common}
    events:
      - s3:                 # とりあえず良く使うS3のcreateオブジェクトを設定
          bucket:
            Ref: S3Bucket
          event: s3:ObjectCreated:*
          existing: true
          rules:
            - suffix: .csv

resources:
  Resources:
    S3Bucket:                           # S3を作成
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.sampleS3BucketName}
```

functions/main/hander.py
横着して全部1モジュールに書いていますが、関数ごとにファイルの分割等は行ってください・・・

```python
import json
import os
import boto3
from botocore.exceptions import ClientError
import base64
import gspread
from oauth2client.service_account import ServiceAccountCredentials


def get_secret():
    # これはSecrets Managerを作成するときのサンプルコードのほぼそのままです
    try:
        secret = None
        decoded_binary_secret = None

        secret_name = os.environ['KEYNAME']
        region_name = "ap-northeast-1"

        # Create a Secrets Manager client
        session = boto3.session.Session()
        client = session.client(
            service_name='secretsmanager',
            region_name=region_name
        )

        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )

    except ClientError as e:
        if e.response['Error']['Code'] == 'DecryptionFailureException':
            raise e
        elif e.response['Error']['Code'] == 'InternalServiceErrorException':
            raise e
        elif e.response['Error']['Code'] == 'InvalidParameterException':
            raise e
        elif e.response['Error']['Code'] == 'InvalidRequestException':
            raise e
        elif e.response['Error']['Code'] == 'ResourceNotFoundException':
            raise e
    else:
        if 'SecretString' in get_secret_value_response:
            secret = get_secret_value_response['SecretString']
        else:
            decoded_binary_secret = base64.b64decode(
                get_secret_value_response['SecretBinary'])

    # Your code goes here.
    return decoded_binary_secret.decode()


def connect_gspread(jsonf, key):
    scope = ['https://spreadsheets.google.com/feeds',
             'https://www.googleapis.com/auth/drive']
    credentials = ServiceAccountCredentials.from_json_keyfile_name(
        jsonf, scope)
    gc = gspread.authorize(credentials)
    SPREADSHEET_KEY = key
    worksheet = gc.open_by_key(SPREADSHEET_KEY).sheet1
    return worksheet


def google_test(event, context):

    #APIにはファイルで渡すので/tmpに出力しましょう。
    jsonf = "/tmp/google-access.json"

    with open(jsonf, mode='w') as f:
        f.write(get_secret())

    spread_sheet_key = '1o3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    ws = connect_gspread(jsonf, spread_sheet_key)

    # A1 セルにhoge1って入れます
    ws.update_cell(1, 1, "hoge1")

    body = {
        "message": "{} !".format("finished ."),
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

```


## デプロイ

```
## layersからデプロイする
cd functions/layers
npm install
pip install -r requirements.txt
sls deploy

## main関数をデプロイする
cd functions/main
sls deploy
```

実行！！！


無事A1セルにhoge1がはいりました！

![スクリーンショット 2020-10-26 16-37-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344266/02960a2c-a9ba-caf9-37a7-f1080e7f5c52.png)


ということでAWS lambdaからspread sheetを更新できました。
まあ、GASで書けばいいんじゃん？っていうツッコミもありますが、こんなことがしたくなってしまったかたは
ご参考までにしていただけると幸いです。

ではよいAWSライフを！