WEBサービス開発グループの堀です。こんにちわ。
桜満開のニュースよりスギ花粉のシーズンが終盤に入ったというニュースのほうが個人的にはうれしい今日この頃ですが、いかがお過ごしでしょうか。今回はiTunes Connectからレポートデータを取得するツールReporterを実行するLambda関数をAWSサービスの勉強も兼ねて作りました、という話です。
概要
ReporterはAppleが提供しているツールで、iTunes Connectから売上やDL数などのレポート(テキスト/tsv形式)をダウンロードすることができます。ただ、Javaのプログラムな上に色々と融通が利かないところがあり、Google PlayのREST APIなどと比べると扱いづらいツールです。
今回はPython3経由でこのツールを実行して取得したレポートファイルをAmazon S3にアップロードするLambda関数を作成してみました。
作業の段取りとしては、以下の流れになります。
- ダウンロードしたレポートファイルを格納するためのAmazon S3バケットを用意する
- Cloud9の環境を作る
- Reporter一式(Reporter.jar、Reporter.propertes)をダウンロードしてLambda関数内ディレクトリに展開する
- Lambda関数を実装 / deployする
- AWSコンソール画面から各種設定を行う(Lambda、IAM(KMS))
Python3での開発環境があって、手動でzip圧縮とdeploy作業を行うのであればCloud9を使用しなくても大丈夫です。
Amazon S3バケットを用意する
バケットの作成手順は特に難しいところもありませんので割愛しますが、Cloud9を使う関係でリージョンは米国東部(バージニア北部)にしました。
Cloud9の環境を作る
2018/4/2現在、米国東部(バージニア北部/オハイオ)、米国西部(オレゴン)、アジアパシフィック(シンガポール)、EU(アイルランド)で利用可となっていますが、S3のバケットと同じリージョンであればどこでもよいと思います。Cloud9の設定項目は後からでも変更できますので、とりあえずはデフォルト値で問題ないと思います。
環境が作成されて統合開発環境っぽい画面が表示されたら、右上端にある歯車アイコンを押して設定画面を開き、PYTHONPATHを確認しておきましょう。2018/4/3現在、python3.6がなぜかpathに入っておらず、python3.6を使用したときに一部ライブラリが正常にimportできなくなっていますので、3.6のpathを追加しておきます。あと、Python Versionも「Python3」に変更しておきます。
- PYTHONPATH : /usr/local/lib/python3.6/dist-packages
- Python Version: Python3
Lambda関数の作成は右側のAWS Resourcesタブにある「λ+」アイコンを押して始めます。
Cloud9では2018/4/2現在、node.jsとpythonが利用でき、S3にアクセスするといった雛形(Blueprint)が選べるようになっています。今回は「s3-get-object-python3」を使用していますが、python3.6のempty-pythonでも問題ありません。設定項目は後で変更できますので、その他の設定はひとまずデフォルト値にしておきます。
関数の雛形が作成されたら、画面下のコンソール画面からReporter一式(Reporter.jar、Reporter.propertes)をwgetなどでダウンロードして関数のディレクトリ配下に展開しておきます。
1 2 3 4 5 |
~/environment $ wget https://itunespartner.apple.com/assets/downloads/Reporter.zip ~/environment $ unzip Reporter.zip ~/environment $ cp Reporter/Reporter.* LambdaReporter/ ~/environment $ vim LambdaReporter/Reporter.properties (1行目を消す) |
Reporter.propertiesの先頭1行目にアクセストークンを記述するところがありますが、アクセストークンはLambdaの環境変数から取得して埋め込むようにしますので先頭1行目は消しておきます。iTunes Connectのアクセストークンの取得の仕方はReporterに記載されていますので、そちらを参照してください。
Lambda関数の実装とdeploy
処理の流れとしては、
- Reporter一式(Reporter.jar、Reporter.propert)を/tmpに移動
- アクセストークンをLambdaの環境変数から取得してReporter.propertiesに追記
- Reporter.jarを/tmpで実行してレポートを取得
- 取得したレポートファイルをunzipしてAmazon S3にアップロード
のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
import urllib.parse import boto3 import subprocess import shutil import os from datetime import datetime from datetime import timedelta from base64 import b64decode print('Loading function') s3 = boto3.resource('s3') def lambda_handler(event, context): # init init() # get Sales Report from itunes connect ret_array = get_sales_report() # upload report to S3 try: str_list = ret_array[2].split('.')[0:2] file_name = '.'.join(str_list) bucket = s3.Bucket(【バケット名】) bucket.upload_file('/tmp/'+file_name, '【バケット名】/'+file_name) except Exception as err: print(err) return False def get_sales_report(): try: # move work directory os.chdir('/tmp') # get Vendor id cmd = 'java -jar Reporter.jar p=Reporter.properties Sales.getVendors' ret = subprocess.run(cmd.split(), stdout=subprocess.PIPE) vendor_id = ret.stdout.decode() # get Report from itunes connect date = (datetime.now() - timedelta(days=2)).strftime('%Y%m%d') cmd = 'java -jar Reporter.jar p=Reporter.properties Sales.getReport ' + vendor_id + ', Sales, Summary, Daily, ' + date ret = subprocess.run(cmd.split(), stdout=subprocess.PIPE) ret_array = ret.stdout.decode().split() if ret_array[0] != 'Successfully': raise NameError('Reporter.jar execute failed') subprocess.run(['gunzip',ret_array[2]], stdout=subprocess.PIPE) except Exception as err: print(err) return False return ret_array def init(): try: # copy Reporter to work directory if os.path.isfile('/tmp/Reporter.jar') != True: shutil.copy('Reporter.jar', '/tmp/') if os.path.isfile('/tmp/Reporter.properties') != True: shutil.copy('Reporter.properties', '/tmp/') # get AccessToken token = get_token() if token == False: return False # add AccessToken with open('/tmp/Reporter.properties','a') as file: file.write('AccessToken='+token) except shutil.Error as err: print(err) return False return True def get_token(): kms = boto3.client('kms') decrypt_text = kms.decrypt(CiphertextBlob=b64decode(os.environ['IC_TOKEN']))['Plaintext'] return decrypt_text.decode() |
1.のようにReporter.*を/tmpに移す理由として、
- AWS Lambdaの仕様で書き込みができるのは/tmpに限定されている
- Reporterではレポートのダウンロード先ディレクトリを指定できない(実行したディレクトリに置かれる)
- Reporterは引数でReporter.propertiesの位置をディレクトリパス込みで指定できない
といった事情があります。
アクセストークンは環境変数IC_TOKENに暗号化して保存しているため復号してから使用します。
実装が終わったら、画面右側のメニューからAWS Lambdaにdeployします。
Lambda関数の各種設定
お試しでテスト実行だけすることにしますので、特にトリガーは設定しません。実行ロールにはLambdaの実行権限とS3へのアクセス権限が必要になります。デフォルトの実行時間は短すぎるので60秒程度がよいと思います。
環境変数にアクセストークンを設定しますが、暗号化するためには事前にIAMの暗号化キーメニューでキーを作成しておく必要があります。
環境変数に値を入力後に暗号化の設定でKMSキーを指定、「暗号化」を押すと暗号化されて保存されます。
動作テスト
今回はイベント引数は使用していないので、テストイベントの登録は適当で大丈夫です。登録が終わったら「テスト」を押して実行してみて、結果がS3にアップロードされれば成功です。
おわりに
レポートファイルを少し加工してS3にアップロードしておけば、Amazon Athenaを使ってredashなどでアプリのダウンロード数をグラフ化するといったことも簡単にできそうです。Reporterの仕様でアクセストークンの有効期限が6ヶ月となっているため、環境変数に入れておくより処理毎に生成して使用したほうがよさそうですが、トークンの生成時にID/PWの入力を求められるため、pexpectライブラリなどを使う必要がありそうです。このあたりもうまくいったら記事にしょうかと思っています。