魂の生命の領域

AWS とか Python とか本読んだ感想とか哲学とか書きます

Amazon Simple Notification Service (SNS) の不要なサブスクリプションを全部消す

概要

存在しない Topic に紐づいた Subscription を一括削除するスクリプトを書いた

背景

SNS (Simple Notification Service)で Topic を作ったとき、ほぼ確実にその Topic に配信されたメッセージをサブスクライブ(購読)するために Subscripiton を登録することになります。

ただ、不要になった Topic を削除してもその Topic に紐づく Subscription は自動で削除されません。

例えば、サービス間の連携のために SQS を Subscription に登録するような CloudFormation のテンプレートを書いてスタックを作成して、そのスタックを削除したとき、 Topic や Queue は(テンプレート内で明確にリソースとして宣言されているので)削除されますが、その Topic から Queue に配信するという紐づけである Subscription は削除されません。

マネジメントコンソール上からぽちぽちと削除することはできますが、(触ったことある方ならわかるかと思いますが) Subscripiton 一覧の画面上で選択するときはチェックボックスではなくラジオボタンになっているため、一つずつしか削除できません。

なのでスクリプトを書いて一気に消しちゃおうぜというだけの話です。

ソースコード

こんなコードを書きました。

import boto3

sns = boto3.client('sns')

topic_list = []
list_topics_paginator = sns.get_paginator('list_topics')
for response in list_topics_paginator.paginate():
    for item in response['Topics']:
        topic_list.append(item['TopicArn'])

sub_topic_list = []
list_subscriptions_paginator = sns.get_paginator('list_subscriptions')
for response in list_subscriptions_paginator.paginate():
    for item in response['Subscriptions']:
        sub_topic_list.append(
            (item['SubscriptionArn'], item['TopicArn'])
        )

for elem in sub_topic_list:
    if elem[1] not in topic_list:
        sns.unsubscribe(SubscriptionArn=elem[0])

可読性の鬼みたいなわかりやすいコードですが一応軽くなぞります。

まず topic_list には存在する Topic ARN の一覧が格納されます。 Boto3 のメソッドは こちら です。

Topic ARN は arn:aws:sns:{Region}:{AccountId}:{TopicName} のようになるのでアカウントとリージョンが定まれば Topic 名と Topic ARN が一対一対応します。そしてboto3.client('service_name') でクライアントを生成するとき、裏でアカウントとリージョンを宣言したファイルを読んでいるので、Topic Name だけ指定すれば AWS 上で 一意に特定することができていますよね、って感じです(なぜ説明したのかわからなくなってきた)。

同じように存在する Subscripiton の一覧を取得しますが、先ほどと異なる点として 紐づく Topic も一緒に取得 します。 メソッドは こちら です。

ここで取得できる Topic ARN が既に存在しないものであっても普通に取得できてしまいます。 マネコンでもさも存在するかのようなリンクが貼られていてそれを開くとエラーになったりしますからね。

最後の for 文で取得できた Subscription 一覧に対してそれに紐づく Topic が冒頭で取得した「存在する Topic の一覧」に存在しなかったら削除する(サブスクリプションを解除する)、という具合です。

こういうスクリプトは Topic や Subscription が増えてくるとより効力を発揮するので最初からページネーションを考慮しています。

今まであまり深く考えずに client.list_topics()client.list_subscriptions() の戻り値に入ってくる nexeToken を見てもう一度メソッド実行して……というのをやっていましたが、これが一番楽ですね。