-
Notifications
You must be signed in to change notification settings - Fork 7
feat: wso2 api subscription #59
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
Open
MarcioMeier
wants to merge
13
commits into
flaviostutz:main
Choose a base branch
from
MarcioMeier:feat/wso2-api-subscription
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
dd2bf04
feat: wso2 api subscription
MarcioMeier 8986c81
Apply suggestions from code review
MarcioMeier cbf3e2f
chore: lint suggestions from code review
MarcioMeier b4b5fd3
chore: update type definitions
MarcioMeier 9ad4a46
chore: add failIfExists capabilities
MarcioMeier 4f681b2
feat: apply defaults for retry options
MarcioMeier ad1c3c0
feat: allow 404 status code for deleting subscriptions
MarcioMeier 3fab069
chore: enhance types and zod schema
MarcioMeier 4db369c
test: add utility fot nock wso2 sdk
MarcioMeier e97f39e
test: add tests for wso2 api subscription handler
MarcioMeier 5e84deb
test: use utility in wso2 api handler test
MarcioMeier 821c1a7
chore(docs): enhance the construct docs
MarcioMeier ea609d8
test: add tests for api subscription construct
MarcioMeier File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| /* eslint-disable no-console */ | ||
| import { AxiosInstance } from 'axios'; | ||
| import { CdkCustomResourceEvent, CdkCustomResourceResponse } from 'aws-lambda'; | ||
|
|
||
| import { prepareAxiosForWso2Calls } from '../../wso2-utils'; | ||
| import { truncateStr } from '../../utils'; | ||
| import { Wso2ApiSubscriptionProps } from '../types'; | ||
|
|
||
| import { | ||
| createWso2ApiSubscription, | ||
| findWso2ApiSubscription, | ||
| getWso2Api, | ||
| getWso2Application, | ||
| removeWso2ApiSubscription, | ||
| updateWso2ApiSubscription, | ||
| } from './wso2-v1'; | ||
|
|
||
| export type Wso2ApiCustomResourceEvent = CdkCustomResourceEvent & { | ||
| ResourceProperties: Wso2ApiSubscriptionProps; | ||
| }; | ||
|
|
||
| export type Wso2ApiCustomResourceResponse = CdkCustomResourceResponse & { | ||
| Data?: { | ||
| Wso2ApiId?: string; | ||
| SubscriptionId?: string; | ||
| ApplicationId?: string; | ||
| Error?: unknown; | ||
| }; | ||
| Status?: 'SUCCESS' | 'FAILED'; | ||
| Reason?: string; | ||
| }; | ||
|
|
||
| export const handler = async ( | ||
| event: Wso2ApiCustomResourceEvent, | ||
| ): Promise<Wso2ApiCustomResourceResponse> => { | ||
| const response: Wso2ApiCustomResourceResponse = { | ||
| StackId: event.StackId, | ||
| RequestId: event.RequestId, | ||
| LogicalResourceId: event.LogicalResourceId, | ||
| }; | ||
|
|
||
| try { | ||
| console.log('>>> Prepare WSO2 API client...'); | ||
| const wso2Axios = await prepareAxiosForWso2Calls(event.ResourceProperties.wso2Config); | ||
|
|
||
| if (event.RequestType === 'Create' || event.RequestType === 'Update') { | ||
| console.log('>>> Creating or Updating WSO2 API Subscription...'); | ||
|
|
||
| const { wso2ApiId, subscriptionId, applicationId } = await createOrUpdateWso2ApiSubscription( | ||
| event, | ||
| wso2Axios, | ||
| ); | ||
|
|
||
| return { | ||
| ...response, | ||
| PhysicalResourceId: subscriptionId, | ||
| Data: { | ||
| Wso2ApiId: wso2ApiId, | ||
| SubscriptionId: subscriptionId, | ||
| ApplicationId: applicationId, | ||
| }, | ||
| Status: 'SUCCESS', | ||
| }; | ||
| } | ||
|
|
||
| if (event.RequestType === 'Delete') { | ||
| console.log('>>> Deleting WSO2 API...'); | ||
|
|
||
| await removeWso2ApiSubscription({ | ||
| wso2Axios, | ||
| subscriptionId: event.PhysicalResourceId, | ||
| retryOptions: event.ResourceProperties.retryOptions, | ||
| }); | ||
|
|
||
| return { | ||
| ...response, | ||
| PhysicalResourceId: event.PhysicalResourceId, | ||
| Status: 'SUCCESS', | ||
| }; | ||
| } | ||
| throw new Error('Unrecognized RequestType'); | ||
| } catch (error) { | ||
| console.log(`An error has occurred. err=${error}`); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const err = error as any; | ||
| if (err.stack) { | ||
| console.log(err.stack); | ||
| } | ||
| throw new Error(truncateStr(`${error}`, 1000)); | ||
| } | ||
| }; | ||
|
|
||
| const createOrUpdateWso2ApiSubscription = async ( | ||
| event: Wso2ApiCustomResourceEvent, | ||
| wso2Axios: AxiosInstance, | ||
| ): Promise<{ wso2ApiId: string; subscriptionId: string; applicationId: string }> => { | ||
| console.log('Searching for the API in WSO2...'); | ||
MarcioMeier marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const wso2Api = await getWso2Api({ | ||
| wso2Axios, | ||
| apiId: event.ResourceProperties.apiId, | ||
| wso2Tenant: event.ResourceProperties.wso2Config.tenant, | ||
| apiSearchParameters: event.ResourceProperties.apiSearchParameters, | ||
| }); | ||
|
|
||
MarcioMeier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const wso2Application = await getWso2Application({ | ||
| wso2Axios, | ||
| applicationId: event.ResourceProperties.applicationId, | ||
| applicationSearchParameters: event.ResourceProperties.applicationSearchParameters, | ||
| }); | ||
|
|
||
| const wso2Subscription = await findWso2ApiSubscription({ | ||
| wso2Axios, | ||
| apiId: wso2Api.id!, | ||
| applicationId: wso2Application.applicationId!, | ||
| }); | ||
|
|
||
| if ( | ||
| wso2Subscription && | ||
| wso2Subscription.throttlingPolicy === event.ResourceProperties.throttlingPolicy | ||
| ) { | ||
| console.log('Current subscription already exists with the same configuration. Skipping update'); | ||
| return { | ||
| wso2ApiId: wso2Api.id!, | ||
| subscriptionId: wso2Subscription.subscriptionId!, | ||
| applicationId: wso2Application.applicationId!, | ||
| }; | ||
| } | ||
|
|
||
| if (wso2Subscription) { | ||
| console.log('Subscription already exists. Updating...'); | ||
| const result = await updateWso2ApiSubscription({ | ||
| wso2Axios, | ||
| subscriptionId: wso2Subscription.subscriptionId!, | ||
| apiId: wso2Api.id!, | ||
| applicationId: wso2Application.applicationId!, | ||
| throttlingPolicy: event.ResourceProperties.throttlingPolicy, | ||
| retryOptions: event.ResourceProperties.retryOptions, | ||
| }); | ||
|
|
||
| return { | ||
| wso2ApiId: wso2Api.id!, | ||
| subscriptionId: result.subscriptionId!, | ||
| applicationId: wso2Application.applicationId!, | ||
| }; | ||
| } | ||
|
|
||
| console.log('Creating a new subscription...'); | ||
| const result = await createWso2ApiSubscription({ | ||
| wso2Axios, | ||
| apiId: wso2Api.id!, | ||
| applicationId: wso2Application.applicationId!, | ||
| throttlingPolicy: event.ResourceProperties.throttlingPolicy, | ||
| retryOptions: event.ResourceProperties.retryOptions, | ||
| }); | ||
|
|
||
| return { | ||
| wso2ApiId: wso2Api.id!, | ||
| subscriptionId: result.subscriptionId!, | ||
| applicationId: wso2Application.applicationId!, | ||
| }; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| /* eslint-disable no-console */ | ||
| import { AxiosInstance } from 'axios'; | ||
| import { backOff } from 'exponential-backoff'; | ||
|
|
||
| import { Wso2ApiSubscriptionProps } from '../types'; | ||
| import { findWso2Api, getWso2ApiById } from '../../wso2-api/handler/wso2-v1'; | ||
| import { ApiFromListV1 } from '../../wso2-api/v1/types'; | ||
| import { | ||
| findWso2Application, | ||
| getWso2ApplicationById, | ||
| } from '../../wso2-application/handler/wso2-v1'; | ||
| import { Wso2ApplicationInfo } from '../../wso2-application/v1/types'; | ||
| import { Subscription, SubscriptionList } from '../v1/types'; | ||
|
|
||
| export type GetWso2ApiArgs = Pick<Wso2ApiSubscriptionProps, 'apiId' | 'apiSearchParameters'> & { | ||
| wso2Axios: AxiosInstance; | ||
| wso2Tenant?: string; | ||
| }; | ||
|
|
||
| export const getWso2Api = async ({ | ||
| wso2Axios, | ||
| apiId, | ||
| wso2Tenant, | ||
| apiSearchParameters, | ||
| }: GetWso2ApiArgs): Promise<ApiFromListV1> => { | ||
| if (apiId) { | ||
| console.log('Getting WSO2 API by id...'); | ||
| const apiDetails = await getWso2ApiById({ wso2Axios, wso2ApiId: apiId }); | ||
| return apiDetails; | ||
| } | ||
|
|
||
| if (!apiSearchParameters) { | ||
| throw new Error('apiSearchParameters is required for searching API'); | ||
| } | ||
|
|
||
| console.log('Getting WSO2 API by search parameters...'); | ||
| const apiDetails = await findWso2Api({ | ||
| wso2Axios, | ||
| apiContext: apiSearchParameters.context, | ||
| apiName: apiSearchParameters.name, | ||
| apiVersion: apiSearchParameters.version, | ||
| wso2Tenant, | ||
| }); | ||
|
|
||
| if (!apiDetails) { | ||
| throw new Error('Cannot find the WSO2 API is related to this Custom Resource.'); | ||
MarcioMeier marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| return apiDetails; | ||
| }; | ||
|
|
||
| export type GetWso2ApplicationArgs = Pick< | ||
| Wso2ApiSubscriptionProps, | ||
| 'applicationId' | 'applicationSearchParameters' | ||
| > & { | ||
| wso2Axios: AxiosInstance; | ||
| }; | ||
|
|
||
| export const getWso2Application = async ({ | ||
| wso2Axios, | ||
| applicationId, | ||
| applicationSearchParameters, | ||
| }: GetWso2ApplicationArgs): Promise<Wso2ApplicationInfo> => { | ||
| if (applicationId) { | ||
| console.log('Getting WSO2 Application by id...'); | ||
| const application = await getWso2ApplicationById({ wso2Axios, applicationId }); | ||
| return application; | ||
| } | ||
|
|
||
| if (!applicationSearchParameters) { | ||
| throw new Error('applicationSearchParameters is required for searching application'); | ||
| } | ||
|
|
||
| console.log('Getting WSO2 API by search parameters...'); | ||
| const application = await findWso2Application({ | ||
| wso2Axios, | ||
| name: applicationSearchParameters.name, | ||
| }); | ||
|
|
||
| if (!application) { | ||
| throw new Error('Cannot find the WSO2 application is related to this Custom Resource.'); | ||
| } | ||
|
|
||
| return application; | ||
| }; | ||
|
|
||
| export type FindWso2ApiSubscriptionArgs = { | ||
| wso2Axios: AxiosInstance; | ||
| apiId: string; | ||
| applicationId: string; | ||
| }; | ||
| export const findWso2ApiSubscription = async ({ | ||
| wso2Axios, | ||
| apiId, | ||
| applicationId, | ||
| }: FindWso2ApiSubscriptionArgs): Promise<Subscription | undefined> => { | ||
| const apil = await wso2Axios.get<SubscriptionList>(`/api/am/store/v1/subscriptions`, { | ||
| params: { | ||
| apiId, | ||
| applicationId, | ||
| }, | ||
| }); | ||
|
|
||
| const apiRes = apil.data.list; | ||
|
|
||
| if (!apiRes) { | ||
| throw new Error('find subscription response is empty'); | ||
| } | ||
|
|
||
| if (apiRes.length > 1) { | ||
| throw new Error( | ||
| `More than one subscription found for api '${apiId}' and application '${applicationId}' so we cannot determine it's id automatically`, | ||
| ); | ||
| } | ||
|
|
||
| if (apiRes.length === 0) { | ||
| // eslint-disable-next-line no-undefined | ||
| return undefined; | ||
| } | ||
|
|
||
| const existingSubscription = apiRes[0]; | ||
| console.log( | ||
| `Found existing WSO2 Subscription. subscriptionId=${existingSubscription.subscriptionId};`, | ||
| ); | ||
|
|
||
| return existingSubscription; | ||
| }; | ||
|
|
||
| export type GetWso2ApiSubscriptionByIdArgs = { | ||
| wso2Axios: AxiosInstance; | ||
| subscriptionId: string; | ||
| }; | ||
|
|
||
| const getWso2ApiSubscriptionById = async ({ | ||
| wso2Axios, | ||
| subscriptionId, | ||
| }: GetWso2ApiSubscriptionByIdArgs): Promise<Subscription> => { | ||
| const res = await wso2Axios.get<Subscription>(`/api/am/store/v1/subscriptions/${subscriptionId}`); | ||
| return res.data; | ||
| }; | ||
|
|
||
| export type CreateWso2ApiSubscriptionArgs = Pick<Wso2ApiSubscriptionProps, 'retryOptions'> & { | ||
| wso2Axios: AxiosInstance; | ||
| apiId: string; | ||
| applicationId: string; | ||
| throttlingPolicy: string; | ||
| }; | ||
|
|
||
| export const createWso2ApiSubscription = async ({ | ||
| wso2Axios, | ||
| apiId, | ||
| applicationId, | ||
| throttlingPolicy, | ||
| retryOptions, | ||
| }: CreateWso2ApiSubscriptionArgs): Promise<Subscription> => { | ||
| const payload: Subscription = { | ||
| applicationId, | ||
| apiId, | ||
| throttlingPolicy, | ||
| }; | ||
|
|
||
| const res = await backOff( | ||
| async () => wso2Axios.post(`/api/am/store/v1/subscriptions`, payload), | ||
| retryOptions?.mutationRetries, | ||
| ); | ||
|
|
||
| // wait for Application to be created by retrying checks | ||
| await backOff(async () => { | ||
| await getWso2ApiSubscriptionById({ | ||
| wso2Axios, | ||
| subscriptionId: res.data.subscriptionId!, | ||
| }); | ||
| }, retryOptions?.checkRetries); | ||
|
|
||
| return res.data; | ||
| }; | ||
|
|
||
| export type UpdateWso2ApiSubscriptionArgs = CreateWso2ApiSubscriptionArgs & { | ||
| subscriptionId: string; | ||
| }; | ||
|
|
||
| export const updateWso2ApiSubscription = async ({ | ||
| wso2Axios, | ||
| subscriptionId, | ||
| apiId, | ||
| applicationId, | ||
| throttlingPolicy, | ||
| retryOptions, | ||
| }: UpdateWso2ApiSubscriptionArgs): Promise<Subscription> => { | ||
| const payload: Subscription = { | ||
| applicationId, | ||
| apiId, | ||
| throttlingPolicy, | ||
| }; | ||
|
|
||
| const res = await backOff( | ||
| async () => wso2Axios.post(`/api/am/store/v1/subscriptions/${subscriptionId}`, payload), | ||
| retryOptions?.mutationRetries, | ||
| ); | ||
|
|
||
| return res.data; | ||
| }; | ||
|
|
||
| export type RemoveWso2ApiSubscriptionArgs = Pick<Wso2ApiSubscriptionProps, 'retryOptions'> & { | ||
| wso2Axios: AxiosInstance; | ||
| subscriptionId: string; | ||
| }; | ||
|
|
||
| export const removeWso2ApiSubscription = async ({ | ||
| wso2Axios, | ||
| subscriptionId, | ||
| retryOptions, | ||
| }: RemoveWso2ApiSubscriptionArgs): Promise<void> => { | ||
| await backOff( | ||
| async () => wso2Axios.delete(`/api/am/store/v1/subscriptions/${subscriptionId}`), | ||
| retryOptions?.mutationRetries, | ||
| ); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { Wso2BaseProperties } from '../types'; | ||
|
|
||
| export type Wso2ApiSubscriptionProps = Wso2BaseProperties & { | ||
| apiId?: string; | ||
| apiSearchParameters?: { | ||
| name: string; | ||
| version: string; | ||
| context: string; | ||
| }; | ||
| applicationId?: string; | ||
| applicationSearchParameters?: { | ||
| name: string; | ||
| }; | ||
| throttlingPolicy: 'Unlimited' | 'Gold' | 'Silver' | 'Bronze'; | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.