GCP - App Engine アプリケーション構築チュートリアル GCS編

[GCP - App Engine アプリケーション構築チュートリアル](https://qiita.com/minarai/private/ca7e8aa6f2aa753ac999)の続きです。
App Engineのセットアップやサービスアカウントの作成が済んでない場合は済ませてください。

# GCS(Cloud Storage)にバケットを作成
App Engineに送信されたファイルはGCSのバケットへ保存したいので[コンソール](https://console.cloud.google.com/storage/browser)からバケットを作成します。

1. コンソールに入ったら「バケットを作成」リンクをクリック
2. バケット名を入力し続行をクリック
3. ロケーションタイプで「Region」を選択し「asia-northeast1(東京)」を選択して続行をクリック
4. ストレージクラスで「Standard」を選択し続行をクリック
5. アクセス制御で「きめ細かい管理」を選択し続行をクリック
6. 詳細設定で暗号化項目で「Googleが管理する鍵」を選択
7. （省略化）　保持ポリシーの設定を行う
8. （省略化）　ラベルの追加をクリックしてラベルを追加する
9. 作成ボタンをクリック

# アプリケーション構成

#### ディレクトリ構造
ソースは[Git Hub](https://github.com/kirakirapq/gcp-samples/tree/master/gcs-sample)にあります。

```
|-- gcs-sample
|   |-- .gcloudignore
|   |-- app.yaml
|   |-- main.py
|   `-- requirements.txt
```

#### .gcloudignoreについて
app engineデプロイに含めたくないファイルを宣言

```.gcloudignore
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
#   $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

# Python pycache:
__pycache__/
# Ignored by the build system
/setup.cfg
```

#### app.yamlについて
アプリケーションコンフィグの宣言をします

```app.yaml
runtime: python37
env: standard
service: default
entrypoint: gunicorn -b :$PORT main:app

env_variables:
  # 環境変数を追加
  BUCKET_NAME: バケット名
  PROJECT_ID: プロジェクトID

automatic_scaling:
  min_idle_instances: automatic
  max_idle_instances: automatic
  min_pending_latency: automatic
  max_pending_latency: automatic
```

#### main.pyについて

```main.py
import logging
import os

from flask import Flask, request, jsonify
from google.cloud import storage

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

ALLOWED_EXTENSIONS = {'csv'}

def get_file(file_name):
  """GSCからデータを読み込む

  Arguments:
      fname {string} -- 対象のファイル名を指定
  """
  bucket_name = os.environ.get('BUCKET_NAME')
  project_name = os.environ.get('PROJECT_ID')

  #プロジェクト名を指定してclientを作成
  gcs = storage.Client(project_name)
  #バケット名を指定してbucketを取得
  bucket = gcs.get_bucket(bucket_name)
  #Blobを作成
  blob = storage.Blob(file_name, bucket)
  content = blob.download_as_string()
 
  return content

@app.route('/gcs-api/file',  methods=['GET'])
def index():
  file_name = request.args.get('file_name', default='', type=str)
  data = get_file(file_name)
  
  return data
  return jsonify({
    "result": data
  })

@app.route('/gcs-api/file/<string:file_name>', methods=['GET'])
def get_data(file_name=''):
    data = get_file(file_name)
  
    return data

@app.route('/gcs-api/upload',  methods=['POST'])
def upload():
  uploaded_file = request.files.get('file')

  if not uploaded_file:
        return 'No file uploaded.', 400
  bucket_name = os.environ.get('BUCKET_NAME')
  project_name = os.environ.get('PROJECT_ID')

  file_name = uploaded_file.filename
  content   = uploaded_file.read()

  logging.debug(file_name)
  
  # Create a Cloud Storage client.
  gcs = storage.Client(project_name)

  # Get the bucket that the file will be uploaded to.
  bucket = gcs.get_bucket(bucket_name)

  # Create a new blob and upload the file's content.
  blob = bucket.blob(file_name)

  blob.upload_from_string(
        content,
        content_type=uploaded_file.content_type
    )

  return blob.public_url

@app.errorhandler(500)
def server_error(e):
    logging.exception('An error occurred during a request.')
    return """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(e), 500

if __name__ == '__main__':
  app.run()
```

#### requirements.txtについて
メインスクリプトやシステムが利用しているパッケージなどを宣言します

```requirements.txt
Flask==1.1.2
google-cloud-storage==1.26.0
gunicorn==20.0.4; python_version > '3.0'
gunicorn==19.10.0; python_version < '3.0'
```


#### ローカル環境構築
ローカル環境でサンプルアプリケーションを実行する際にはPython SDKに含まれているローカル開発サーバーを使用します。
キットに含まれているローカル開発環境サーバーについてはGCPドキュメント[ローカル開発サーバーの使用](https://cloud.google.com/appengine/docs/standard/python/tools/using-local-server?hl=ja)をご覧ください。
pip installコマンドでパッケージがうまく更新されませんでしたので[こちらの記事](http://tink.elliptium.net/2014/01/15/pip_upgrade_with_a_requirements_txt.html)を参考にしました。

```
% pip install -t lib -r requirements.txt
# 新しいバージョンが適切に入らないときはこれ
% awk 'BEGIN { FS = "==" } { print $1 }' requirements.txt | xargs pip install --upgrade

# ローカル開発環境で実行
% python main.py
```

Postman（またはcurl)を使ってFile upload

```
% curl --location --request POST 'http://127.0.0.1:5000/gcs-api/upload' \
--form 'file=@/path/to/test.csv'
https://storage.googleapis.com/バケット名/test.csv
```

Postman（またはcurl)を使ってファイルデータを取得

```
% curl --location --request GET 'http://127.0.0.1:5000/gcs-api/file/test.csv'id,title,price
1,"hogehoge",2000
2,"hoge",1500
```

`Control + C`でローカルサーバーシャットダウン

# App Engineへデプロイ

```
% gcloud app deploy
```

#### uploadテスト

```
% curl --location --request POST 'https://プロジェクトID.an.r.appspot.com/gcs-api/upload' \
--form 'file=@/path/to/file/test.csv'
https://storage.googleapis.com/バケット名/test.csv
```

#### 取得テスト

```
curl --location --request GET 'https://プロジェクトID.an.r.appspot.com/gcs-api/file/test.csv'
api/file/test.csv'id,title,price
1,"hogehoge",2000
2,"hoge",1500
```

この記事は「[GSC - Cloud Storage の使用](https://cloud.google.com/appengine/docs/flexible/python/using-cloud-storage?hl=ja)」を参考に作成しました