-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SNS Tag update #221
SNS Tag update #221
Changes from all commits
92f999b
1c743a1
4e99ff1
88370fb
ff57c4b
ed649a8
1dc024c
563c171
4ad7c3b
3e2175b
45bf912
4772660
dda6b52
bbdd9e2
edb47d1
9213aae
9473b60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { | ||
type CreateTopicCommandInput, | ||
type SNSClient, | ||
TagResourceCommand, | ||
paginateListTopics, | ||
} from '@aws-sdk/client-sns' | ||
import { | ||
|
@@ -12,11 +13,12 @@ import { | |
SetTopicAttributesCommand, | ||
UnsubscribeCommand, | ||
} from '@aws-sdk/client-sns' | ||
import type { Either } from '@lokalise/node-core' | ||
import { type Either, isError } from '@lokalise/node-core' | ||
import { calculateOutgoingMessageSize as sqsCalculateOutgoingMessageSize } from '@message-queue-toolkit/sqs' | ||
|
||
import type { ExtraSNSCreationParams } from '../sns/AbstractSnsService' | ||
|
||
import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts' | ||
import { generateTopicSubscriptionPolicy } from './snsAttributeUtils' | ||
|
||
type AttributesResult = { | ||
|
@@ -82,13 +84,19 @@ export async function assertTopic( | |
topicOptions: CreateTopicCommandInput, | ||
extraParams?: ExtraSNSCreationParams, | ||
) { | ||
const command = new CreateTopicCommand(topicOptions) | ||
const response = await snsClient.send(command) | ||
|
||
if (!response.TopicArn) { | ||
throw new Error('No topic arn in response') | ||
let topicArn: string | ||
try { | ||
const command = new CreateTopicCommand(topicOptions) | ||
const response = await snsClient.send(command) | ||
if (!response.TopicArn) throw new Error('No topic arn in response') | ||
topicArn = response.TopicArn | ||
} catch (err) { | ||
// We only manually build ARN in case of tag update | ||
if (!extraParams?.forceTagUpdate) throw err | ||
// To build ARN we need topic name and error should be "topic already exist with different tags" | ||
if (!topicOptions.Name || !isTopicAlreadyExistWithDifferentTagsError(err)) throw err | ||
topicArn = await buildTopicArn(snsClient, topicOptions.Name) | ||
} | ||
const topicArn = response.TopicArn | ||
|
||
if (extraParams?.queueUrlsWithSubscribePermissionsPrefix || extraParams?.allowedSourceOwner) { | ||
const setTopicAttributesCommand = new SetTopicAttributesCommand({ | ||
|
@@ -102,6 +110,13 @@ export async function assertTopic( | |
}) | ||
await snsClient.send(setTopicAttributesCommand) | ||
} | ||
if (extraParams?.forceTagUpdate) { | ||
const tagTopicCommand = new TagResourceCommand({ | ||
ResourceArn: topicArn, | ||
Tags: topicOptions.Tags, | ||
}) | ||
await snsClient.send(tagTopicCommand) | ||
} | ||
|
||
return topicArn | ||
} | ||
|
@@ -178,3 +193,35 @@ export async function getTopicArnByName(snsClient: SNSClient, topicName?: string | |
*/ | ||
export const calculateOutgoingMessageSize = (message: unknown) => | ||
sqsCalculateOutgoingMessageSize(message) | ||
|
||
const isTopicAlreadyExistWithDifferentTagsError = (error: unknown) => | ||
!!error && | ||
isError(error) && | ||
'Error' in error && | ||
!!error.Error && | ||
typeof error.Error === 'object' && | ||
'Code' in error.Error && | ||
'Message' in error.Error && | ||
typeof error.Error.Message === 'string' && | ||
error.Error.Code === 'InvalidParameter' && | ||
error.Error.Message.includes('already exists with different tags') | ||
|
||
/** | ||
* Manually builds the ARN of a topic based on the current AWS account and the topic name. | ||
* It follows the following pattern: arn:aws:sns:<region>:<account-id>:<topic-name> | ||
* Doc -> https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html | ||
*/ | ||
const buildTopicArn = async (client: SNSClient, topicName: string) => { | ||
const region = | ||
typeof client.config.region === 'string' ? client.config.region : await client.config.region() | ||
|
||
const stsClient = new STSClient({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. recreating client on every request sounds like a lot of overhead, I'd suggest to pass it as a dependency instead There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean as a dependency for users? the idea was to hide its usage by using SNS config parameters, although I am not really sure if it is a good idea 🤔. What do you mean by recreating it in every request? I think it is done just once on init right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I got it, you mean to the method, would be fine to create it on publisher/consumer creation and pass it as parameter? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wouldn't it be done once per buildTopicArn, which realistically means once for every topic? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, same as we pass sqsClient and snsClient There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But as a dependency passed by the user, or do you think it is a good idea to build it internally base on SNS config? Advantage of not requesting it is that the API won’t change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Btw sorry, I am already a bit slow 😓) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would pass by user, would be less surprising than globally cached dependency. but we can also make an optional parameter and build automatically if not passed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense to me! Will think about it and address the change in the new PR |
||
endpoint: client.config.endpoint, | ||
region, | ||
credentials: client.config.credentials, | ||
endpointProvider: client.config.endpointProvider, | ||
}) | ||
const identityResponse = await stsClient.send(new GetCallerIdentityCommand({})) | ||
|
||
return `arn:aws:sns:${region}:${identityResponse.Account}:${topicName}` | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expose this from utils, this can be useful in a variety of different use-cases.
Would be nice to also write some tests for it, to ensure that the built arn matches the real arn created by SNS based on same parameters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm, Okay! will expose it 🙏