Skip to content
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

feat(coherence deploy): add setup deploy coherence #8234

Merged
merged 14 commits into from
May 17, 2023
27 changes: 27 additions & 0 deletions docs/docs/deploy/coherence.md
jtoar marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
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

You have to have your project 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

If you want to deploy your redwood project on Coherence:
1. If you don't already have one, create a new redwood project: `yarn create redwood-app ./test-deploy`
3. run the command `yarn rw setup deploy coherence`. The command will inspect your `prisma` config to determine if you are using a supported database (`postgres` or `mysql` are supported on Coherence)
4. 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:
- redis server
- database migration/seeding/snapshot loading
- cron jobs or async workers
- object storage using Google Cloud Storage or AWS's S3
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)
}
183 changes: 183 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,183 @@
import fs from 'fs'
import path from 'path'

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 }) {
const addCoherenceFilesTask = await getAddCoherenceFilesTask(force)

const tasks = new Listr(
[
addCoherenceFilesTask,
updateRedwoodTOMLPortsTask(),
printSetupNotes(notes),
],
{ rendererOptions: { collapse: false } }
)

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

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

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,
})
}

const notes = [
"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.",
]

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

/**
* 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)) {
notes.unshift(
'⚠️ Warning: only mysql and postgresql prisma databases are supported on Coherence at this time.\n'
)
}

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

return coherenceFiles.yamlTemplate(db)
}

/**
* Updates the ports in redwood.toml to use an environment variable.
*/
function updateRedwoodTOMLPortsTask() {
return {
title: 'Updating ports in redwood.toml...',
task: () => {
const redwoodTOMLPath = path.join(
redwoodProjectPaths.base,
'redwood.toml'
)

let redwoodTOMLContent = fs.readFileSync(redwoodTOMLPath, 'utf-8')

redwoodTOMLContent = redwoodTOMLContent.replace(
/port.*?\n/m,
'port = "${PORT}"\n'
)

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

// ------------------------
// 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
# adapter: postgresql

migration: ["yarn", "rw", "prisma", "migrate", "deploy"]
jtoar marked this conversation as resolved.
Show resolved Hide resolved
jtoar marked this conversation as resolved.
Show resolved Hide resolved

web:
type: frontend
assets_path: "web/dist"
prod:
command: ["yarn", "rw", "serve", "web"]
dev:
command: ["yarn", "rw", "dev", "web", "--fwd=\"--allowed-hosts all\""]
build: ["yarn", "rw", "build", "web", "--no-prerender"]
jtoar marked this conversation as resolved.
Show resolved Hide resolved
local_packages: ["node_modules"]

system:
cpu: 2
memory: 2G
`
},
healthCheck: `\
// Coherence health check
export const handler = async () => {
return {
statusCode: 200,
}
}
`,
}