Skip to content

Commit

Permalink
feat(coherence deploy): add setup deploy coherence (#8234)
Browse files Browse the repository at this point in the history
* feat: add setup deploy coherence

* add changes to yaml template

* Update packages/cli/src/commands/setup/deploy/providers/coherenceHandler.js

* warn on prerender, style

* add warning to docs

* properly escape quotes

* configure toml

* throw instead of warn

* style: move notes inline

* properly try-catch

* add to intro

* add note about data migrations
  • Loading branch information
jtoar committed May 17, 2023
1 parent 185a8ed commit 6e788e7
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 0 deletions.
40 changes: 40 additions & 0 deletions docs/docs/deploy/coherence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
description: Serverful deploys on GCP or AWS via Coherence's full-lifecycle environment automation
---

# Deploy to Coherence

[Coherence](https://www.withcoherence.com/) delivers automated environments across the full software development lifecycle, without requiring you to glue together your own mess of open source tools to get a world-class develper experience for your team. Coherence is focused on serving startups, who are doing mission-critical work. With one simple configuration, Coherence offers:

- Cloud-hosted development environments, based on VSCode. Similar to Gitpod or GitHub CodeSpaces
- Production-ready CI/CD running in your own GCP/AWS account, including: database migration/seeding/snapshot loading, parallelized tests, container building and docker registry management
- Full-stack branch previews. Vercel/Netlify-like developer experience for arbitrary container apps, including dependencies such as CDN, redis, and database resources
- Staging and production environment management in your AWS/GCP accounts. Production runs in its own cloud account (AWS) or project (GCP). Integrated secrets management across all environment types with a developer-friendly UI

## Coherence Prerequisites

To deploy to Coherence, your Redwood project needs to be hosted on GitHub and you must have an [AWS](https://docs.withcoherence.com/docs/tutorials/creating-an-app-on-aws) or [GCP](https://docs.withcoherence.com/docs/tutorials/creating-an-app-on-gcp) account.

## Coherence Deploy

:::caution Prerender doesn't work with Coherence yet

You can see its current status and follow updates here on GitHub: https://github.com/redwoodjs/redwood/issues/8333.

But if you don't use prerender, carry on!

:::

If you want to deploy your Redwood project on Coherence, run the setup command:

```
yarn rw setup deploy coherence
```

The command will inspect your Prisma config to determine if you're using a supported database (at the moment, only `postgres` or `mysql` are supported on Coherence).

Then follow the [Coherence Redwood deploy docs](https://docs.withcoherence.com/docs/configuration/frameworks#redwood-js) for more information, including if you want to set up:
- a redis server
- database migration/seeding/snapshot loading
- cron jobs or async workers
- object storage using Google Cloud Storage or AWS's S3
1 change: 1 addition & 0 deletions docs/docs/deploy/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Redwood is designed for both serverless and traditional infrastructure deploymen

Currently, these are the officially supported deploy targets:
- Baremetal (physical server that you have SSH access to)
- [Coherence](https://www.withcoherence.com/)
- [Flightcontrol.dev](https://www.flightcontrol.dev?ref=redwood)
- [Edg.io](https://edg.io)
- [Netlify.com](https://www.netlify.com/)
Expand Down
5 changes: 5 additions & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ module.exports = {
items: [
{ type: 'doc', label: 'Introduction', id: 'deploy/introduction' },
{ type: 'doc', label: 'Baremetal', id: 'deploy/baremetal' },
{
type: 'doc',
label: 'GCP or AWS via Coherence',
id: 'deploy/coherence',
},
{
type: 'doc',
label: 'AWS via Flightcontrol',
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/commands/setup/deploy/providers/coherence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const command = 'coherence'

export const description = 'Setup Coherence deploy'

export function builder(yargs) {
yargs.option('force', {
description: 'Overwrite existing configuration',
type: 'boolean',
default: false,
})
}

export async function handler(options) {
const { handler } = await import('./coherenceHandler')
return handler(options)
}
241 changes: 241 additions & 0 deletions packages/cli/src/commands/setup/deploy/providers/coherenceHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import fs from 'fs'
import path from 'path'

import toml from '@iarna/toml'
import { getSchema, getConfig } from '@prisma/internals'
import { Listr } from 'listr2'

import {
colors as c,
getPaths,
isTypeScriptProject,
} from '@redwoodjs/cli-helpers'
import { errorTelemetry } from '@redwoodjs/telemetry'

import { printSetupNotes, addFilesTask } from '../helpers'

const redwoodProjectPaths = getPaths()

const EXTENSION = isTypeScriptProject ? 'ts' : 'js'

export async function handler({ force }) {
try {
const addCoherenceFilesTask = await getAddCoherenceFilesTask(force)

const tasks = new Listr(
[
addCoherenceFilesTask,
updateRedwoodTOMLTask(),
printSetupNotes([
"You're ready to deploy to Coherence! ✨\n",
'Go to https://app.withcoherence.com to create your account and setup your cloud or GitHub connections.',
'Check out the deployment docs at https://docs.withcoherence.com for detailed instructions and more information.\n',
"Reach out to redwood@withcoherence.com with any questions! We're here to support you.",
]),
],
{ rendererOptions: { collapse: false } }
)

await tasks.run()
} catch (e) {
errorTelemetry(process.argv, e.message)
console.error(c.error(e.message))
process.exit(e?.exitCode || 1)
}
}

// ------------------------
// Tasks and helpers
// ------------------------

/**
* Adds a health check file and a coherence.yml file by introspecting the prisma schema.
*/
async function getAddCoherenceFilesTask(force) {
const files = [
{
path: path.join(redwoodProjectPaths.api.functions, `health.${EXTENSION}`),
content: coherenceFiles.healthCheck,
},
]

const coherenceConfigFile = {
path: path.join(redwoodProjectPaths.base, 'coherence.yml'),
}

coherenceConfigFile.content = await getCoherenceConfigFileContent()

files.push(coherenceConfigFile)

return addFilesTask({
title: `Adding coherence.yml and health.${EXTENSION}`,
files,
force,
})
}

/**
* Check the value of `provider` in the datasource block in `schema.prisma`:
*
* ```prisma title="schema.prisma"
* datasource db {
* provider = "sqlite"
* url = env("DATABASE_URL")
* }
* ```
*/
async function getCoherenceConfigFileContent() {
const prismaSchema = await getSchema(redwoodProjectPaths.api.dbSchema)
const prismaConfig = await getConfig({ datamodel: prismaSchema })

let db = prismaConfig.datasources[0].activeProvider

if (!SUPPORTED_DATABASES.includes(db)) {
throw new Error(
`Coherence doesn't support the "${db}" provider. To proceed, switch to one of the following: ` +
SUPPORTED_DATABASES.join(', ')
)
}

if (db === 'postgresql') {
db = 'postgres'
}

return coherenceFiles.yamlTemplate(db)
}

const SUPPORTED_DATABASES = ['mysql', 'postgresql']

/**
* should probably parse toml at this point...
* if host, set host
* Updates the ports in redwood.toml to use an environment variable.
*/
function updateRedwoodTOMLTask() {
return {
title: 'Updating redwood.toml...',
task: () => {
const redwoodTOMLPath = path.join(
redwoodProjectPaths.base,
'redwood.toml'
)
let redwoodTOMLContent = fs.readFileSync(redwoodTOMLPath, 'utf-8')
const redwoodTOMLObject = toml.parse(redwoodTOMLContent)

// Replace or add the host
// How to handle matching one vs the other...
if (!redwoodTOMLObject.web.host) {
const [beforeWeb, afterWeb] = redwoodTOMLContent.split(/\[web\]\s/)
redwoodTOMLContent = [
beforeWeb,
'[web]\n host = "0.0.0.0"\n',
afterWeb,
].join('')
}

if (!redwoodTOMLObject.api.host) {
const [beforeApi, afterApi] = redwoodTOMLContent.split(/\[api\]\s/)
redwoodTOMLContent = [
beforeApi,
'[api]\n host = "0.0.0.0"\n',
afterApi,
].join('')
}

redwoodTOMLContent = redwoodTOMLContent.replaceAll(
HOST_REGEXP,
(match, spaceBeforeAssign, spaceAfterAssign) =>
['host', spaceBeforeAssign, '=', spaceAfterAssign, '"0.0.0.0"'].join(
''
)
)

// Replace the apiUrl
redwoodTOMLContent = redwoodTOMLContent.replace(
API_URL_REGEXP,
(match, spaceBeforeAssign, spaceAfterAssign) =>
['apiUrl', spaceBeforeAssign, '=', spaceAfterAssign, '"/api"'].join(
''
)
)

// Replace the web and api ports.
redwoodTOMLContent = redwoodTOMLContent.replaceAll(
PORT_REGEXP,
(_match, spaceBeforeAssign, spaceAfterAssign, port) =>
[
'port',
spaceBeforeAssign,
'=',
spaceAfterAssign,
`"\${PORT:${port}}"`,
].join('')
)

fs.writeFileSync(redwoodTOMLPath, redwoodTOMLContent)
},
}
}

const HOST_REGEXP = /host(\s*)=(\s*)\".+\"/g
const API_URL_REGEXP = /apiUrl(\s*)=(\s*)\".+\"/
const PORT_REGEXP = /port(\s*)=(\s*)(?<port>\d{4})/g

// ------------------------
// Files
// ------------------------

const coherenceFiles = {
yamlTemplate(db) {
return `\
api:
type: backend
url_path: "/api"
prod:
command: ["yarn", "rw", "build", "api", "&&", "yarn", "rw", "serve", "api", "--apiRootPath=/api"]
dev:
command: ["yarn", "rw", "build", "api", "&&", "yarn", "rw", "dev", "api", "--apiRootPath=/api"]
local_packages: ["node_modules"]
system:
cpu: 2
memory: 2G
health_check: "/health"
resources:
- name: ${path.basename(redwoodProjectPaths.base)}-db
engine: ${db}
version: 13
type: database
${db === 'postgres' ? 'adapter: postgresql' : ''}
# If you use data migrations, add "&&", "yarn", "rw" "data-migrate" "up".
migration: ["yarn", "rw", "prisma", "migrate", "deploy"]
web:
type: frontend
assets_path: "web/dist"
prod:
command: ["yarn", "rw", "serve", "web"]
dev:
command: ["yarn", "rw", "dev", "web", "--fwd=\\"--allowed-hosts all\\""]
# Heads up: Redwood's prerender doesn't work with Coherence yet.
# For current status and updates, see https://github.com/redwoodjs/redwood/issues/8333.
build: ["yarn", "rw", "build", "web", "--no-prerender"]
local_packages: ["node_modules"]
system:
cpu: 2
memory: 2G
`
},
healthCheck: `\
// Coherence health check
export const handler = async () => {
return {
statusCode: 200,
}
}
`,
}

0 comments on commit 6e788e7

Please sign in to comment.