-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(coherence deploy): add setup deploy coherence (#8234)
* 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
Showing
5 changed files
with
303 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
packages/cli/src/commands/setup/deploy/providers/coherence.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
241
packages/cli/src/commands/setup/deploy/providers/coherenceHandler.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 [email protected] 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, | ||
} | ||
} | ||
`, | ||
} |