Skip to content

Commit

Permalink
feat(ci-cd): add apis to manage plan size configuration and features … (
Browse files Browse the repository at this point in the history
#39)

This PR adds the API endspoints to manage plan size configuration and
features mapping with the plans to be created by SaaS Admin and consumed
by both application plane and control plane.
  • Loading branch information
shubhamp-sf authored Aug 22, 2024
1 parent 3a7aa05 commit 5e03dcb
Show file tree
Hide file tree
Showing 43 changed files with 3,994 additions and 1,503 deletions.
16 changes: 15 additions & 1 deletion services/orchestrator-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ This microservice provides following loopback services and providers which can b
5. [Tenant Deprovisioning Provider](#5-tenant-deprovisioning-provider)
6. [Tenant Provisioning Success Handler](#6-tenant-provisioning-success-provider)
7. [Tenant Provisioning Failure Handler](#7-tenant-provisioning-failure-provider)
8. [Tenant Deployment Handler](#8-tenant-deployment-handler)

Here's the invocation flow for quick understanding on how and when above artifacts are called, more details are provided in their own sections:

Expand Down Expand Up @@ -135,6 +136,7 @@ import {
TenantProvisioningFailureHandler,
TenantProvisioningHandler,
TenantProvisioningSuccessHandler,
TenantDeploymentHandler,
} from '@arc-saas/orchestrator-service';

export interface AWSEventBridgeInterface {
Expand All @@ -160,6 +162,8 @@ export class OrchestratorService implements OrchestratorServiceInterface {
private handleTenantProvisioningSuccess: TenantProvisioningSuccessHandler,
@inject(OrchestratorServiceBindings.TENANT_PROVISIONING_FAILURE_HANDLER)
private handleTenantProvisioningFailure: TenantProvisioningFailureHandler,
@inject(OrchestratorServiceBindings.TENANT_DEPLOYMENT_HANDLER)
private handleTenantDeployment: TenantDeploymentHandler,
) {}

handleEvent(
Expand All @@ -173,8 +177,10 @@ export class OrchestratorService implements OrchestratorServiceInterface {
return this.handleTenantDeprovisioning(eventBody.detail);
case EventTypes.TENANT_PROVISIONING_SUCCESS:
return this.handleTenantProvisioningSuccess(eventBody.detail);
case EventTypes.TENANT_PROVISIONING_FAILED:
case EventTypes.TENANT_PROVISIONING_FAILURE:
return this.handleTenantProvisioningFailure(eventBody.detail);
case DefaultEventTypes.TENANT_DEPLOYMENT:
return this.handleTenantDeployment(eventBody);
default:
throw new Error(`Unsupported event type: ${eventType}`);
}
Expand Down Expand Up @@ -519,6 +525,14 @@ For example, In this provider you can implement any cleanup or notification need

The way of binding this provider is similar to other provider, just the binding key is `OrchestratorServiceBindings.TENANT_PROVISIONING_FAILURE_HANDLER`.

#### 8. Tenant Deployment Provider

This provider is intended to be the handler of the tenant deployment, which means any work after the provisioning that is required to make the tenant's application up and running, should be implemented here.

For example, this can be adding some initial users. Populating some seed data in the application service etc.

The way of binding this provider is similar to other provider, just the binding key is `OrchestratorServiceBindings.TENANT_DEPLOYMENT_HANDLER`.

## Deployment

The @arc-saas/orchestrator-service can be deployed in various ways, including as a serverless application. Here's how you can set it up for serverless deployment, specifically for AWS Lambda.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
OrchestratorService,
BuilderService,
TierDetailsFn,
TenantDeploymentHandlerProvider,
} from './../../';
import {OrchestratorServiceComponent} from '../../component';
import {Provider} from '@loopback/context';
Expand Down Expand Up @@ -85,6 +86,8 @@ describe('OrchestratorServiceComponent', () => {
TenantProvisioningSuccessHandlerProvider,
[OrchestratorServiceBindings.TENANT_PROVISIONING_FAILURE_HANDLER.key]:
TenantProvisioningFailureHandlerProvider,
[OrchestratorServiceBindings.TENANT_DEPLOYMENT_HANDLER.key]:
TenantDeploymentHandlerProvider,
};

for (const [key] of Object.entries(providerMap)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
TenantDeprovisioningHandler,
TenantProvisioningSuccessHandler,
TenantProvisioningFailureHandler,
TenantDeploymentHandler,
} from '../..';

describe('OrchestratorService', () => {
Expand All @@ -14,6 +15,7 @@ describe('OrchestratorService', () => {
let tenantDeprovisioningHandlerStub: sinon.SinonStub;
let tenantProvisioningSuccessHandlerStub: sinon.SinonStub;
let tenantProvisioningFailureHandlerStub: sinon.SinonStub;
let tenantDeploymentHandlerStub: sinon.SinonStub;

beforeEach(givenOrchestratorService);

Expand Down Expand Up @@ -50,17 +52,29 @@ describe('OrchestratorService', () => {
).to.be.true();
});

it('handles TENANT_PROVISIONING_FAILED event', async () => {
it('handles TENANT_PROVISIONING_FAILURE event', async () => {
const eventBody = {tenantId: '123', error: 'Some error'};
await orchestratorService.handleEvent(
DefaultEventTypes.TENANT_PROVISIONING_FAILED,
DefaultEventTypes.TENANT_PROVISIONING_FAILURE,
eventBody,
);
expect(
tenantProvisioningFailureHandlerStub.calledOnceWith(eventBody),
).to.be.true();
});

it('handles TENANT_DEPLOYMENT event', async () => {
const eventBody = {
tenantId: '123',
plan: {features: [{key: 'FEAT', value: 'FOO'}]},
};
await orchestratorService.handleEvent(
DefaultEventTypes.TENANT_DEPLOYMENT,
eventBody,
);
expect(tenantDeploymentHandlerStub.calledOnceWith(eventBody)).to.be.true();
});

it('throws error for unsupported event type', async () => {
const eventBody = {};
await expect(
Expand All @@ -76,12 +90,14 @@ describe('OrchestratorService', () => {
tenantDeprovisioningHandlerStub = sinon.stub();
tenantProvisioningSuccessHandlerStub = sinon.stub();
tenantProvisioningFailureHandlerStub = sinon.stub();
tenantDeploymentHandlerStub = sinon.stub();

orchestratorService = new OrchestratorService(
tenantProvisioningHandlerStub as unknown as TenantProvisioningHandler,
tenantDeprovisioningHandlerStub as unknown as TenantDeprovisioningHandler,
tenantProvisioningSuccessHandlerStub as unknown as TenantProvisioningSuccessHandler,
tenantProvisioningFailureHandlerStub as unknown as TenantProvisioningFailureHandler,
tenantDeploymentHandlerStub as unknown as TenantDeploymentHandler,
);
}
});
3 changes: 3 additions & 0 deletions services/orchestrator-service/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
TierDetailsProvider,
TenantProvisioningSuccessHandlerProvider,
TenantDeprovisioningHandlerProvider,
TenantDeploymentHandlerProvider,
} from './services';
import {EventController} from './controllers';
import {RestApplication} from '@loopback/rest';
Expand Down Expand Up @@ -52,6 +53,8 @@ export class OrchestratorServiceComponent implements Component {
TenantProvisioningSuccessHandlerProvider,
[OrchestratorServiceBindings.TENANT_PROVISIONING_FAILURE_HANDLER.key]:
TenantProvisioningFailureHandlerProvider,
[OrchestratorServiceBindings.TENANT_DEPLOYMENT_HANDLER.key]:
TenantDeploymentHandlerProvider,
});

// Bind Service Classes if not provided by consumer of the component
Expand Down
1 change: 1 addition & 0 deletions services/orchestrator-service/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './tenant-provisioning-handler.service';
export * from './tenant-provisioning-success-handler.service';
export * from './tenant-provisioning-failure-handler.service';
export * from './tenant-deprovisioning-handler.service';
export * from './tenant-deployment-handler.service';
export * from './tier-details.service';
export * from './orchestrator.service';
export * from './types';
17 changes: 12 additions & 5 deletions services/orchestrator-service/src/services/orchestrator.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import {
OrchestratorServiceBindings,
OrchestratorServiceInterface,
} from './types';
import {TenantProvisioningHandler} from './tenant-provisioning-handler.service';
import {AnyObject} from '@loopback/repository';
import {TenantDeprovisioningHandler} from './tenant-deprovisioning-handler.service';
import {TenantProvisioningSuccessHandler} from './tenant-provisioning-success-handler.service';
import {TenantProvisioningFailureHandler} from './tenant-provisioning-failure-handler.service';
import {
TenantProvisioningHandler,
TenantDeprovisioningHandler,
TenantProvisioningSuccessHandler,
TenantProvisioningFailureHandler,
TenantDeploymentHandler,
} from './';

@injectable({scope: BindingScope.TRANSIENT})
export class OrchestratorService implements OrchestratorServiceInterface {
Expand All @@ -21,6 +24,8 @@ export class OrchestratorService implements OrchestratorServiceInterface {
private handleTenantProvisioningSuccess: TenantProvisioningSuccessHandler,
@inject(OrchestratorServiceBindings.TENANT_PROVISIONING_FAILURE_HANDLER)
private handleTenantProvisioningFailure: TenantProvisioningFailureHandler,
@inject(OrchestratorServiceBindings.TENANT_DEPLOYMENT_HANDLER)
private handleTenantDeployment: TenantDeploymentHandler,
) {}

async handleEvent(
Expand All @@ -34,8 +39,10 @@ export class OrchestratorService implements OrchestratorServiceInterface {
return this.handleTenantDeprovisioning(eventBody);
case DefaultEventTypes.TENANT_PROVISIONING_SUCCESS:
return this.handleTenantProvisioningSuccess(eventBody);
case DefaultEventTypes.TENANT_PROVISIONING_FAILED:
case DefaultEventTypes.TENANT_PROVISIONING_FAILURE:
return this.handleTenantProvisioningFailure(eventBody);
case DefaultEventTypes.TENANT_DEPLOYMENT:
return this.handleTenantDeployment(eventBody);
default:
throw new Error(`Unsupported event type: ${eventType}`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {injectable, BindingScope, Provider} from '@loopback/core';
import {AnyObject} from '@loopback/repository';

export type TenantDeploymentHandler<T extends AnyObject = {}> = (
body: T,
) => Promise<void>;

@injectable({scope: BindingScope.TRANSIENT})
export class TenantDeploymentHandlerProvider
implements Provider<TenantDeploymentHandler>
{
constructor() {}

value() {
return async (body: AnyObject) => this.handler(body);
}

private async handler(body: AnyObject): Promise<void> {
throw Error(
`${TenantDeploymentHandlerProvider.name} is not implemented. Follow the README for more details.`,
);
}
}
13 changes: 11 additions & 2 deletions services/orchestrator-service/src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {TenantDeprovisioningHandler} from './tenant-deprovisioning-handler.servi
import {AnyObject} from '@loopback/repository';
import {TenantProvisioningSuccessHandler} from './tenant-provisioning-success-handler.service';
import {TenantProvisioningFailureHandler} from './tenant-provisioning-failure-handler.service';
import {TenantDeploymentHandler} from './tenant-deployment-handler.service';

const BINDING_PREFIX = `arc-saas`;

Expand All @@ -26,7 +27,11 @@ export namespace OrchestratorServiceBindings {
);
export const TENANT_PROVISIONING_FAILURE_HANDLER =
BindingKey.create<TenantProvisioningFailureHandler>(
`${BINDING_PREFIX}.providers.tenant-provisioning-success-handler`,
`${BINDING_PREFIX}.providers.tenant-provisioning-failure-handler`,
);
export const TENANT_DEPLOYMENT_HANDLER =
BindingKey.create<TenantDeploymentHandler>(
`${BINDING_PREFIX}.providers.tenant-deployment-handler`,
);
export const BUILDER_SERVICE = BindingKey.create<BuilderServiceInterface>(
`${BINDING_PREFIX}.services.builder-service`,
Expand All @@ -52,5 +57,9 @@ export enum DefaultEventTypes {
TENANT_PROVISIONING = 'TENANT_PROVISIONING',
TENANT_DEPROVISIONING = 'TENANT_DEPROVISIONING',
TENANT_PROVISIONING_SUCCESS = 'TENANT_PROVISIONING_SUCCESS',
TENANT_PROVISIONING_FAILED = 'TENANT_PROVISIONING_FAILED',
TENANT_PROVISIONING_FAILURE = 'TENANT_PROVISIONING_FAILURE',

TENANT_DEPLOYMENT = 'TENANT_DEPLOYMENT',
TENANT_DEPLOYMENT_SUCCESS = 'TENANT_DEPLOYMENT_SUCCESS',
TENANT_DEPLOYMENT_FAILURE = 'TENANT_DEPLOYMENT_FAILURE',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

var dbm;
var type;
var seed;
var fs = require('fs');
var path = require('path');
var Promise;

/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
Promise = options.Promise;
};

exports.up = function (db) {
var filePath = path.join(
__dirname,
'sqls',
'20240805112128-remove-plan-items-up.sql',
);
return new Promise(function (resolve, reject) {
fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) {
if (err) return reject(err);
console.log('received data: ' + data);

resolve(data);
});
}).then(function (data) {
return db.runSql(data);
});
};

exports.down = function (db) {
var filePath = path.join(
__dirname,
'sqls',
'20240805112128-remove-plan-items-down.sql',
);
return new Promise(function (resolve, reject) {
fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) {
if (err) return reject(err);
console.log('received data: ' + data);

resolve(data);
});
}).then(function (data) {
return db.runSql(data);
});
};

exports._meta = {
version: 1,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

var dbm;
var type;
var seed;
var fs = require('fs');
var path = require('path');
var Promise;

/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
Promise = options.Promise;
};

exports.up = function (db) {
var filePath = path.join(
__dirname,
'sqls',
'20240805112316-add-plan-sizes-table-up.sql',
);
return new Promise(function (resolve, reject) {
fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) {
if (err) return reject(err);
console.log('received data: ' + data);

resolve(data);
});
}).then(function (data) {
return db.runSql(data);
});
};

exports.down = function (db) {
var filePath = path.join(
__dirname,
'sqls',
'20240805112316-add-plan-sizes-table-down.sql',
);
return new Promise(function (resolve, reject) {
fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) {
if (err) return reject(err);
console.log('received data: ' + data);

resolve(data);
});
}).then(function (data) {
return db.runSql(data);
});
};

exports._meta = {
version: 1,
};
Loading

0 comments on commit 5e03dcb

Please sign in to comment.