AWSの各アカウントのインスタンス一覧を取得する

# このスクリプトの目的
AWSをある程度活用していくと、組織(Organizations)を駆使して複数のAWSアカウントを使い分けることが多いと思います。そのような場合に、横断的に情報を取得するサンプルコードを書いてみました。
データ全取得はそれなりに時間がかかるので、JSONファイルにデータを書き出してから活用するのがおすすめです。AWS SDKの型情報をそのまま使っておけば、何を利用するかはあとで決めることができます。

# 簡単な解説
まず組織内のアカウント一覧を取得し、取得した各アカウントにAssumeRoleしながら情報を収集していきます。
今回はec2インスタンスの取得だけを行っていますが、RDS等も簡単に追加できます。

Info[]型としてデータをまとめてJSONで書き出しているので、あとはこのJSONを読み込んで煮るなり焼くなりしてください。

# サンプルコード

```typescript
import {Account, Accounts} from "aws-sdk/clients/organizations";
import {EC2, Organizations, STS} from "aws-sdk";
import * as fs from "fs";
import {Instance} from "aws-sdk/clients/ec2";
import {CredentialsOptions} from "aws-sdk/lib/credentials";

// 情報を取得するために利用するRole
// プログラムから、各アカウントのこのRoleにAssumeRoleできる必要がある
const AccessRole = "OrganizationAccountAccessRole"

type Info = {
    account: Account
    instances: Instance[]
}

async function assumeRole(account: Account): Promise<CredentialsOptions> {
    const sts = new STS()
    const RoleArn = `arn:aws:iam::${account.Id}:role/${AccessRole}`
    console.log(`Switch to ${RoleArn}`)
    const {Credentials} = await sts.assumeRole({
        RoleArn,
        RoleSessionName: "FetchInstances"
    }).promise()
    if (!Credentials) {
        throw new Error("Failed to switch Role")
    }
    // CredentialsOptionsと大文字小文字が違うので、ここで変換
    return {
        accessKeyId: Credentials.AccessKeyId,
        secretAccessKey: Credentials.SecretAccessKey,
        sessionToken: Credentials.SessionToken,
    }
}

async function getAccounts(org?: Organizations, NextToken?: string): Promise<Accounts> {
    console.log(`getAccount ${NextToken || "initial"}`)
    // ap-northeast-1にはOrganizationsエンドポイントが存在しない。
    if (!org) {
        org = new Organizations({
            region: "us-east-1",
            endpoint: "https://organizations.us-east-1.amazonaws.com",
        })
    }
    let result: Account[] = []
    const accounts = await org.listAccounts({
        NextToken,
    }).promise()
    if (accounts.Accounts) {
        result = result.concat(accounts.Accounts)
    }
    if (accounts.NextToken) {
        result = result.concat(await getAccounts(org, accounts.NextToken))
    }
    return result
}

async function getInstances(ec2: EC2, NextToken?: string): Promise<Instance[]> {
    console.log(`getInstances ${NextToken || "initial"}`)
    const instances = await ec2.describeInstances({
        NextToken
    }).promise()
    if (!instances.Reservations) {
        return []
    }
    let result: Instance[] = []
    for (const r of instances.Reservations) {
        if (r.Instances) {
            result = result.concat(r.Instances)
        }
    }
    if (instances.NextToken) {
        result = result.concat(await getInstances(ec2, instances.NextToken))
    }
    return result
}


(async function run() {
    // 組織内のアカウントを全部取得
    const accounts = await getAccounts()
    const result: Info[] = []
    for (const account of accounts) {
        // 個別のアカウントにAssumeRoleしながら情報を取得する
        console.log(`Processing account ${account.Id} (${account.Name})`)
        const credentials = await assumeRole(account)
        result.push({
            account,
            instances: await getInstances(new EC2({credentials})),
        })
    }
    const filepath = "./result.json"
    console.log(`Write result to ${filepath}`)
    await new Promise(r => fs.writeFile(filepath, JSON.stringify(result, null, 2), r))
})()
```