Kotlin で AWS の AMI, Snapshot を削除する

Kotlin で AWS の AMI, Snapshot を削除したときのコードを紹介します。 Kotlin で AWS を操作したい人の参考になれば幸いです。

## 経緯

昔から溜められた AMI, Snapshot が大量にあって、費用がかさんでいたので Kotlin を使って消しました。
Lambda でバックアップを取るスクリプトが python 2.7 で作られていたのですが、 バックアップを消すスクリプトがなかったというのが根本的な原因です。

(今回のコードは Lambda のコードではなく、 ローカルから実行したコードです。)

## コード

Kotlin といっても、 JVM コンパイル なので、 要するに Java みたいなもんです。

IntelliJ IDEA を開いて gradle Project を作成します。
build.gradle は次のように記述します。 AWS SDK EC2 を読み込むようにしています。 AWS SDK を丸ごと読み込むと大変なので。

```gradle
buildscript {
    ext.kotlin_version = '1.2.40'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

group 'com.improve_future'
version '1.0-SNAPSHOT'

apply plugin: 'kotlin'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    // https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk
    compile group: 'com.amazonaws', name: 'aws-java-sdk-ec2', version: '1.11.319'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
```

`/src/main/kotlin/Main.kt` を作成して下記のコードを追加します。

```kotlin
package com.improve_future.snap_controller

import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.regions.Regions
import com.amazonaws.services.ec2.AmazonEC2ClientBuilder
import com.amazonaws.services.ec2.model.*
import java.text.SimpleDateFormat
import java.util.*

object Owner {
    val accessKey = "......."
    val secretKey = "......."
    val id = "...."

    fun createFilter(): Filter {
        return Filter().
                withName("owner-id").
                withValues(id)
    }
}

val credential = BasicAWSCredentials(
        Owner.accessKey, Owner.secretKey)

val ec2client = AmazonEC2ClientBuilder.standard().
        withRegion(Regions.AP_NORTHEAST_1).
        withCredentials(AWSStaticCredentialsProvider(credential)).
        build()

/**
 * 古いイメージを削除します
 *
 * 150日以上前に作成された特定の AMI を削除します。
 */
fun deregisterOldImage() {
    val dir = DescribeImagesRequest()
    dir.setOwners(listOf(Owner.id))
    // 個人のおかれた環境によりますが、
    // 消したいイメージの名前の先頭で絞り込みます。
    dir.filters.add(
            Filter().
                    withName("name").
                    withValues(
                            "lambda_backup_*",
                            "other_*",
                            "another_*"))
    // 自分が所有しているもののみを対象にします。
    dir.filters.add(Owner.createFilter())
    val result = ec2client.describeImages(dir)
    // ここから取得したイメージの CreationDate を比較するために
    // 日付文字列を作ります。
    // CreationDate は文字列で AWS から返ってきます。
    // 参考: https://stackoverflow.com/questions/47023285/convert-ami-creation-date-from-string-to-datetime-format
    // 150日前の日付文字列を生成して比較し、それより前に作られたイメージを消します。
    var lineString = SimpleDateFormat("yyyy-MM-dd").
            format(
                    Calendar.getInstance().let {
                        it.time = java.util.Date()
                        it.add(Calendar.DATE, -150)
                        it.time})
    // この処理はコルーチンでもいいと思います。
    result.images.forEach { image ->
        if (image.creationDate < lineString) {
            // 一応コンソール上で動きが見えるように文字列を表示します。
            println(image.name)
            println(image.creationDate)
            val dir = DeregisterImageRequest().
                    withImageId(image.imageId)
            ec2client.deregisterImage(dir)
        }
    }
}

/**
 * 使われていない Snapshot を削除します。
 *
 * AWS では AMI を作るとその時に Snapshot が作成されますので、
 * Snapshot は意識しないうちに増えていきます。
 * Snapshot は AMI などで使われていたりすると削除時にエラーが出るので、
 * 参照されていない Snapshot に絞って削除します。
 * 参考: https://qiita.com/ketancho/items/4c5f235ac77884e14bc6
 */
fun deleteNoUseSnapshot() {
    // イメージをすべて取得して、
    // そこに登録されている Snapshot ID を集めます。
    // それらのIDに含まれない Snapshot に限定して削除します。
    val dir = DescribeImagesRequest()
    var imageResult = ec2client.describeImages(dir)
    val snapshotIdList = mutableListOf<String>()
    imageResult.images.forEach {
        it.blockDeviceMappings.forEach {
            // Java のプラットフォーム型なので、 null になる場合があります。
            // 慎重に null の判定を行います。
            it.ebs?.let{
                if (it.snapshotId != null)
                    snapshotIdList.add(it.snapshotId)

            }
        }
    }

    val dsr = DescribeSnapshotsRequest()
    // 自分が保有している Snapshot に絞り込みます。
    dsr.filters.add(Owner.createFilter())
    val snapshots = ec2client.describeSnapshots(dsr)
    snapshots.snapshots.forEach {
        if (!snapshotIdList.contains(it.snapshotId)) {
            // コンソールに削除対象の Snapshot 情報を表示します。
            println(it.snapshotId)
            println(it.state)
            println(it.description)
            var dsr = DeleteSnapshotRequest().
                    withSnapshotId(it.snapshotId)
            ec2client.deleteSnapshot(dsr)
        }
    }
}


fun main(vararg s: String) {
    deregisterOldImage()
    deleteNoUseSnapshot()
}
```