Help us understand the problem. What is going on with this article?

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

概要

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

Untitled (1).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 レイヤー

各モジュールの説明

functions/layers/serverless.yml

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

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

functions/layers/requirements.txt

boto3
botocore
gspread
oauth2client

functions/main/serverless.yml

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モジュールに書いていますが、関数ごとにファイルの分割等は行ってください・・・

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

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

ではよいAWSライフを!

mimimi-no-sesese
普段はマネージメントしてますが、プログラミングは好きです。
hands-lab
ハンズラボは小売業特化型ITソリューション企業です。数十万に及ぶ膨大な商品マスタを扱ってきた豊富なノウハウで、お客様の現場に最適なシステムを提案・開発します。 エンジニア募集中
https://www.hands-lab.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away