Swift3でAWS Kinesis Firehoseを導入してハマったところ

#ユーザーのログを集めたい
iOSのクライアントの動きをログとして集積したいので、その方法としてAWSのCognitoでTemporary Credentialsを各クライアントに発行して、Kinesis Firehoseに集めたいデータを渡してS3に保存することにしました。

Kinesis Firehose（以下Firehose）を使うとKinesis Stream（従来のKinesis: 以下Kinesis）と異なり、いきなりS3やRed Shiftにデータを保存できるのが特徴です。

#参考にしたサイト

- [AWSのiOS developer guide](http://docs.aws.amazon.com/mobile/sdkforios/developerguide/kinesis.html)
これはKinesisとFirehoseのふたつに関して実装手順が書いてありますがObjective-Cで記述されていました。Objective-Cには馴染みがあまりなかったのでこれだけでは実装完了できませんでした。

- [classmethodさんの記事](http://dev.classmethod.jp/cloud/aws/kinesis-stream-ios/)
コンソール画面での手順も書いてあり、これに沿っていけば*Kinesis*の導入まではすぐいけそうです。ただし、コードはSwift3で書かれていないので自分で修正しないといけない点がありました。

#ハマったところ
##1, ユーザーロールに紐づく権限周り（IAM）
###1, Cognitoの権限
Cognitoにはデフォルトの権限に加えてFirehoseにアクセスする権限（最低でも`PutRecord`, `PutRecordBatch`）を付与します。CognitoのFederated IdentitiesのほうでEdit Identity Poolを選択して権限を確かめます。

<img width="1432" alt="Screen Shot 2016-11-28 at 23.31.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/122365/10e91bef-bfd5-4209-c730-a453b3640090.png">
（FirehoseはTokyoリージョンにまだ対応していないのでCognitoもFirehoseもバージニアにしました。）

Authenticated RoleとUnauthenticated Roleに設定している権限を見ると、以下のようなポリシーがデフォルトで入っていました。

```デフォルト.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```

これにIAM画面で以下のようなポリシーがあるので追加しました。

```AmazonKinesisFirehoseFullAccess.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "firehose:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
```

###2, Firehoseの権限
これはデフォルトで準備されるやつに

```
{
            "Effect": "Allow",
            "Action": [
                "firehose:PutRecord",
                "firehose:PutRecordBatch"
            ],
            "Resource": [
                "arn:aws:firehose:us-east-1:account-id:deliverystream/delivery-stream-name"
            ]
        }
```

を付け加えました。

[IAM Simulator](https://policysim.aws.amazon.com/home/index.jsp?#users)で確かめながらやると確実です。

##2, iOSの`submitAllRecords()` のところ

Firehoseに関する記事は少なくてSwift3ではどうすればいいのかわかりにくかったです。
こんな感じで実装してうまくいきました。実際はこれらをシングルトンにまとめました。

```AppDelegate.swift

import AWSCore
import AWSCognito
import AWSKinesis

////

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    ////
    configureAWSCognito()
    
    ////

    return true
}

func configureAWSCognito() {
    // Settings for AWSCognito
    let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .usEast1, identityPoolId: "us-east-1:00000000-xxxx-0000-0000-xxxxxxxxxxxx")
    let configuration = AWSServiceConfiguration(region:.usEast1, credentialsProvider:credentialsProvider)
        
    AWSFirehoseRecorder.register(with: configuration, forKey: "FirehoseName")
        
    AWSServiceManager.default().defaultServiceConfiguration = configuration
    updateIdentifier()
}
    
func updateIdentifier() {
    // Settings for AWSCognito
    let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .usEast1, identityPoolId: "us-east-1:00000000-xxxx-0000-0000-xxxxxxxxxxxx")
    credentialsProvider.getIdentityId().continue({ (task: AWSTask!) -> Any! in
        if (task.error != nil) {
            print("Error: " + task.error!.localizedDescription)
        }
        else {
            // the task result will contain the identity id
            self.cognitoId = task.result as? String
            // print("CognitoId: ")
            // print(task.result as? String)
        }
        return nil
    })
}


```

```データを送る場所.swift

    func record(data: [Data]) {
        guard  let firehoseRecorder = AWSFirehoseRecorder.default() else { return }

        let tasks: [AWSTask] = data.map { firehoseRecorder.saveRecord($0, streamName: "FirehoseName") }
        
        let task = AWSTask(forCompletionOfAllTasks: tasks).continue(successBlock: { (task: AWSTask<AnyObject>) in
            
            return firehoseRecorder.submitAllRecords()
        })
        
        task.continue({ [weak self] (task: AWSTask<AnyObject>) in
            switch task.error {
            case let .some(error):
                print("Error: \(error)")
            case .none:
                print("FirehoseRecorded")
            }
          
            return nil
        })
    }
```


特にハマったのは参考にしていたページの

    AWSTask(forCompletionOfAllTasks: tasks).continueWithSuccessBlock { task -> AnyObject? in
        recorder.submitAllRecords()
    }.continueWithBlock { task -> AnyObject? in
        if let err = task.error {
            NSLog("Error: [%@]", err)
        }
        return nil
    }

の部分を

    let task = AWSTask(forCompletionOfAllTasks: tasks).continue(successBlock: { (task: AWSTask<AnyObject>) in
           
        return firehoseRecorder.submitAllRecords()
    })
        
    task.continue({ [weak self] (task: AWSTask<AnyObject>) in
        switch task.error {
        case let .some(error):
            print("Error: \(error)")
        case .none:
            print("FirehoseRecorded")
        }
          
        return nil
    })

とcontinueを分割した点です。

これでS3に欲しいログをためられるようになりました。