概要
前回の記事の続きです。
前回は下半分を作成したので今回は上半分を作成していきます
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がはいりました!
ということでAWS lambdaからspread sheetを更新できました。
まあ、GASで書けばいいんじゃん?っていうツッコミもありますが、こんなことがしたくなってしまったかたは
ご参考までにしていただけると幸いです。
ではよいAWSライフを!