Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions lib/src/wso2/wso2-api-subscription/handler/index.ts
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...');
const wso2Api = await getWso2Api({
wso2Axios,
apiId: event.ResourceProperties.apiId,
wso2Tenant: event.ResourceProperties.wso2Config.tenant,
apiSearchParameters: event.ResourceProperties.apiSearchParameters,
});

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!,
};
};
218 changes: 218 additions & 0 deletions lib/src/wso2/wso2-api-subscription/handler/wso2-v1.ts
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.');
}

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,
);
};
15 changes: 15 additions & 0 deletions lib/src/wso2/wso2-api-subscription/types.ts
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';
};
Loading
Loading