11import {
22 type CreateTopicCommandInput ,
33 type SNSClient ,
4+ TagResourceCommand ,
45 paginateListTopics ,
56} from '@aws-sdk/client-sns'
67import {
@@ -12,12 +13,14 @@ import {
1213 SetTopicAttributesCommand ,
1314 UnsubscribeCommand ,
1415} from '@aws-sdk/client-sns'
15- import type { Either } from '@lokalise/node-core'
16+ import { type Either , isError } from '@lokalise/node-core'
1617import { calculateOutgoingMessageSize as sqsCalculateOutgoingMessageSize } from '@message-queue-toolkit/sqs'
1718
1819import type { ExtraSNSCreationParams } from '../sns/AbstractSnsService'
1920
21+ import type { STSClient } from '@aws-sdk/client-sts'
2022import { generateTopicSubscriptionPolicy } from './snsAttributeUtils'
23+ import { buildTopicArn } from './stsUtils'
2124
2225type AttributesResult = {
2326 attributes ?: Record < string , string >
@@ -79,16 +82,23 @@ export async function getSubscriptionAttributes(
7982
8083export async function assertTopic (
8184 snsClient : SNSClient ,
85+ stsClient : STSClient ,
8286 topicOptions : CreateTopicCommandInput ,
8387 extraParams ?: ExtraSNSCreationParams ,
8488) {
85- const command = new CreateTopicCommand ( topicOptions )
86- const response = await snsClient . send ( command )
87-
88- if ( ! response . TopicArn ) {
89- throw new Error ( 'No topic arn in response' )
89+ let topicArn : string
90+ try {
91+ const command = new CreateTopicCommand ( topicOptions )
92+ const response = await snsClient . send ( command )
93+ if ( ! response . TopicArn ) throw new Error ( 'No topic arn in response' )
94+ topicArn = response . TopicArn
95+ } catch ( err ) {
96+ // We only manually build ARN in case of tag update
97+ if ( ! extraParams ?. forceTagUpdate ) throw err
98+ // To build ARN we need topic name and error should be "topic already exist with different tags"
99+ if ( ! topicOptions . Name || ! isTopicAlreadyExistWithDifferentTagsError ( err ) ) throw err
100+ topicArn = await buildTopicArn ( stsClient , topicOptions . Name )
90101 }
91- const topicArn = response . TopicArn
92102
93103 if ( extraParams ?. queueUrlsWithSubscribePermissionsPrefix || extraParams ?. allowedSourceOwner ) {
94104 const setTopicAttributesCommand = new SetTopicAttributesCommand ( {
@@ -102,21 +112,28 @@ export async function assertTopic(
102112 } )
103113 await snsClient . send ( setTopicAttributesCommand )
104114 }
115+ if ( extraParams ?. forceTagUpdate && topicOptions . Tags ) {
116+ const tagTopicCommand = new TagResourceCommand ( {
117+ ResourceArn : topicArn ,
118+ Tags : topicOptions . Tags ,
119+ } )
120+ await snsClient . send ( tagTopicCommand )
121+ }
105122
106123 return topicArn
107124}
108125
109- export async function deleteTopic ( client : SNSClient , topicName : string ) {
126+ export async function deleteTopic ( snsClient : SNSClient , stsClient : STSClient , topicName : string ) {
110127 try {
111- const topicArn = await assertTopic ( client , {
128+ const topicArn = await assertTopic ( snsClient , stsClient , {
112129 Name : topicName ,
113130 } )
114131
115- const command = new DeleteTopicCommand ( {
116- TopicArn : topicArn ,
117- } )
118-
119- await client . send ( command )
132+ await snsClient . send (
133+ new DeleteTopicCommand ( {
134+ TopicArn : topicArn ,
135+ } ) ,
136+ )
120137 } catch ( _ ) {
121138 // we don't care it operation has failed
122139 }
@@ -178,3 +195,15 @@ export async function getTopicArnByName(snsClient: SNSClient, topicName?: string
178195 */
179196export const calculateOutgoingMessageSize = ( message : unknown ) =>
180197 sqsCalculateOutgoingMessageSize ( message )
198+
199+ const isTopicAlreadyExistWithDifferentTagsError = ( error : unknown ) =>
200+ ! ! error &&
201+ isError ( error ) &&
202+ 'Error' in error &&
203+ ! ! error . Error &&
204+ typeof error . Error === 'object' &&
205+ 'Code' in error . Error &&
206+ 'Message' in error . Error &&
207+ typeof error . Error . Message === 'string' &&
208+ error . Error . Code === 'InvalidParameter' &&
209+ error . Error . Message . includes ( 'already exists with different tags' )
0 commit comments