Skip to content
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ REDIS_URI=
# Make sure to uncomment the below line and uncomment the REDIS_URI line above
# REDIS_SENTINELS=

# EXPERIMENTAL: Enable dynamic spawning of VM instances when none are available
ENABLE_DYNAMIC_VMS=false
# The amount of random words that should be chosen for a dynamic vm name. Defaults to 2
DYNAMIC_VM_NAME_WORD_COUNT=2
# The maximum amount of servers that can be deployed dynamically
MAX_VM_COUNT=10

# Optional: the JSON Google Application Credentials used for gcloud.driver
# GOOGLE_APPLICATION_CREDENTIALS=

Expand Down
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,39 +84,39 @@ To run `@cryb/portals` in development mode, run `yarn dev`.
It is recommended that in production you run `yarn build`, then `yarn start`.

### Adding a custom provider
`@cryb/portals` makes it easy to add a custom cloud provider to deploy Portal instances onto.
`@cryb/portals` makes it easy to add a custom cloud provider to deploy Server instances onto.

1. First, make a config file under `src/config/providers`. You want to call this `foo.config.ts`. This file should export a method that returns the API of the provider you want to use. See `example.config.ts` or `gcloud.config.ts` for an example of how Google Cloud intergration is handled.
2. Next, make a file under `src/drivers`. You want to call this `foo.driver.ts`. You can copy the code in `example.driver.ts` as a starting point.
3. Import your `foo.config.ts` file then create an instance of the client using the `createClient` method exported in the config file.
4. Then add the code to create a cloud deployment with the desired config under the `try {` clause in the `openPortalInstance` method.
5. *Optional, but recommended* Add the method under the `try {` clause in `closePortalInstance` to destroy the VM instance. This will be called when a Room no longer needs a portal, such as when all members have gone offline.
4. Then add the code to create a cloud deployment with the desired config under the `try {` clause in the `openServerInstance` method.
5. *Optional, but recommended* Add the method under the `try {` clause in `closeServerInstance` to destroy the VM instance. This will be called when the server is no longer needed, such as when there are no Rooms running.
6. Now, under `src/drivers/router.ts`, import your driver and rename its methods so they don't conflict when any other drivers. See below:
```ts
import {
openPortalInstance as openFooPortalInstance,
closePortalInstance as closeFooPortalInstance
openServerInstance as openFooServerInstance,
closeServerInstance as closeFooServerInstance
} from './foo.driver'
```
7. *If you're not using TypeScript, skip this step* Make sure you have added the name of your driver to the `Driver` type. See below:
```ts
type Driver = 'gcloud' | 'kubernetes' | 'foo'
type Driver = 'gcloud' | 'kubernetes' | 'foo' | null
```
8. Add a `case` to the `switch` statement under `openPortalInstance` with the name of your driver methods. See below:
8. Add a `case` to the `switch` statement under `openServerInstance` with the name of your driver methods. See below:
```ts
switch(driver) {
...
case 'foo':
openFooPortalInstance(portal)
openFooServerInstance(portal)
break
}
```
9. *Optional, but recommended* If you added a `closePortalInstance` handler in your driver, add a `case` to the `switch` statement under `closePortalInstance` with the name of your driver methods. See below:
9. *Optional, but recommended* If you added a `closeServerInstance` handler in your driver, add a `case` to the `switch` statement under `closeServerInstance` with the name of your driver methods. See below:
```ts
switch(driver) {
...
case 'foo':
closeFooPortalInstance(portal)
closeFooServerInstance(portal)
break
}
```
Expand All @@ -128,5 +128,4 @@ const fetchCurrentDriver = () => 'foo' as Driver
Done! Enjoy using `@cryb/portals` with the cloud provider of your preferred choice. For any help, view [here](#questions-/-issues). If you're feeling generous, create a [PR request](https://github.com/crybapp/portals) with your driver so the community can use it. Be sure to follow our [Guidelines](https://github.com/crybapp/guidelines) before submitting a PR.

## Questions / Issues

If you have an issues with `@cryb/portals`, please either open a GitHub issue, contact a maintainer or join the [Cryb Discord Server](https://discord.gg/ShTATH4) and ask in #tech-support.
10 changes: 10 additions & 0 deletions src/config/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default {
/**
* Boolean dependant on if dynamic VMs are enabled
*/
dynamic_vms_enabled: process.env.ENABLE_DYNAMIC_VMS === 'true',
/**
* The amount of words that should be generated for a dynamic vm deployment name
*/
dynamic_vm_word_count: parseInt(process.env.DYNAMIC_VM_NAME_WORD_COUNT || '2') || 2
}
4 changes: 2 additions & 2 deletions src/config/providers/example.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const createClient = () => {
return {
createServer: async () => {},
destroyServer: async () => {}
createServer: async (name: string) => {},
destroyServer: async (name: string) => {}
}
}
28 changes: 28 additions & 0 deletions src/config/words.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default [
'alfa',
'bravo',
'charlie',
'delta',
'echo',
'foxtrot',
'golf',
'hotel',
'india',
'juliett',
'kilo',
'lima',
'mike',
'november',
'oscar',
'papa',
'quebec',
'romeo',
'sierra',
'tango',
'uniform',
'victor',
'whiskey',
'xray',
'yankee',
'zulu'
]
31 changes: 22 additions & 9 deletions src/controllers/index.controller.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
import express from 'express'

import Portal from '../models/portal'
import PortalRequest from '../models/request/defs'

import { pushQueue } from '../services/queue.service'
import { closePortal } from '../drivers/portal.driver'
import authenticate from '../server/middleware/authenticate.middleware'

const app = express()

app.post('/create', authenticate, (req, res) => {
app.post('/create', authenticate, async (req, res) => {
const { roomId } = req.body, request: PortalRequest = { roomId, recievedAt: Date.now() }
pushQueue(request)

res.send(request)

try {
const portal = await new Portal().create(request)

res.send(portal)
} catch(error) {
console.error(error)
res.sendStatus(500)
}
})

app.delete('/:id', authenticate, (req, res) => {
app.delete('/:id', authenticate, async (req, res) => {
const { id: portalId } = req.params
closePortal(portalId)
console.log('recieved request to close portal', portalId)

try {
const portal = await new Portal().load(portalId)
await portal.destroy()

res.sendStatus(200)
res.sendStatus(200)
} catch(error) {
console.error(error)
res.sendStatus(500)
}
})

export default app
29 changes: 11 additions & 18 deletions src/drivers/example.driver.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,30 @@
import Portal from '../models/portal'

// Import the API you wish to use
import { createClient } from '../config/providers/example.config'
import { closePortal } from './portal.driver'

import { registerDeployment, deregisterDeployment } from './utils/register.utils.driver'

export const openPortalInstance = async (portal: Portal) => {
const client = createClient(),
name = `portal-${portal.id}`
export const openServerInstance = async () => {
const client = createClient()

try {
// Create the server using the API & Provider of your choice
await client.createServer()
await portal.updateStatus('starting')
const { name } = await registerDeployment('example')
await client.createServer(name)

console.log(`opened portal with name ${name}`)
console.log(`opened portal using example.driver`)
} catch(error) {
closePortal(portal.id)

console.error('error while opening portal', error)
}
}

export const closePortalInstance = async (portal: Portal) => {
const client = createClient(),
name = `portal-${portal.id}`,
{ serverId } = portal
export const closeServerInstance = async (name: string) => {
const client = createClient()

try {
// Destroy the server using the id of the server you stored when creating the server
await client.destroyServer()
await deregisterDeployment(name)
await client.destroyServer(name)

console.log(`closed portal with name ${name}`)
console.log(`closed portal using example.driver`)
} catch(error) {
console.error('error while closing portal', error.response ? error.response.body : error)
}
Expand Down
34 changes: 11 additions & 23 deletions src/drivers/gcloud.driver.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,41 @@
import Portal from '../models/portal'

import { createClient, fetchCredentials } from '../config/providers/gcloud.config'
import { closePortal } from './portal.driver'

import { registerDeployment, deregisterDeployment } from './utils/register.utils.driver'

const { project_id: projectId } = fetchCredentials() || { project_id: null },
zoneId = 'us-east1-b',
baseUrl = `https://www.googleapis.com/compute/v1/projects/${projectId}/zones/${zoneId}/`

export const openPortalInstance = async (portal: Portal) => {
export const openServerInstance = async () => {
const client = createClient()
if(!client) throw 'The Google Cloud driver is incorrect. This may be due to improper ENV variables, please try again'

const portalName = `portal-${portal.id}`

try {
const instanceTemplate = `https://www.googleapis.com/compute/v1/projects/${projectId}/global/instanceTemplates/portal-template`
const { name } = await registerDeployment('gcloud'),
instanceTemplate = `https://www.googleapis.com/compute/v1/projects/${projectId}/global/instanceTemplates/portal-template`

// Create a VM under the template 'portal-template' with the name 'portal-{id}'
await client.request({
url: `${baseUrl}instances?sourceInstanceTemplate=${instanceTemplate}`,
method: 'POST',
data: {
name: portalName
}
data: { name }
})

await portal.updateStatus('starting')

console.log(`opened portal with name ${portalName}`)
console.log('opened server using gcloud.driver with name', name)
} catch(error) {
closePortal(portal.id)

console.error('error while opening portal', error)
}
}

export const closePortalInstance = async (portal: Portal) => {
export const closeServerInstance = async (name: string) => {
const client = createClient()
if(!client) throw 'The Google Cloud driver is incorrect. This may be due to improper ENV variables, please try again'

const portalName = `portal-${portal.id}`

try {
await client.request({
url: `${baseUrl}instances/${portalName}`,
method: 'DELETE'
})
await deregisterDeployment(name)
await client.request({ url: `${baseUrl}instances/${name}`, method: 'DELETE' })

console.log(`closed portal with name ${portalName}`)
console.log('closed server using gcloud.driver')
} catch(error) {
console.error('error while closing portal', error.response ? error.response.body : error)
}
Expand Down
Loading