Skip to content

Commit

Permalink
Deprecate serverless aws deploy as an officially supported option (re…
Browse files Browse the repository at this point in the history
…dwoodjs#8060)

* Revert "remove serverless deploy option (redwoodjs#7993)"

This reverts commit 7847bab.

* Deprecate Serverless Deploy; revert removal
  • Loading branch information
thedavidprice authored Apr 25, 2023
1 parent 0c53801 commit 37b4df3
Show file tree
Hide file tree
Showing 5 changed files with 561 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/docs/deploy/serverless.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ description: Deploy to AWS with Serverless Framework

# Deploy to AWS with Serverless Framework

>⚠️ **Deprecated**
>As of Redwood v5, we are deprecating this deploy setup as an "officially" supported provider. This means:
>- For projects already using this deploy provider, there will be NO change at this time
>- Both the associated `setup` and `deploy` commands will remain in the framework as is; when setup is run, there will be a “deprecation” message
>- We will no longer run CI/CD on the Serverless-AWS deployments, which means we are no longer guaranteeing this deploy works with each new version
>- We are exploring better options to deploy directly to AWS Lambdas; the current deploy commands will not be removed until we find a replacement
>
>For more details (e.g. why?) and current status, see the Forum post ["Deprecating support for Serverless Framework Deployments to AWS Lambdas"](https://community.redwoodjs.com/t/deprecating-support-for-serverless-framework-deployments-to-aws-lambdas/4755/10)
>The following instructions assume you have read the [General Deployment Setup](./introduction.md#general-deployment-setup) section above.
Yes, the name is confusing, but Serverless provides a very interesting option—deploy to your own cloud service account and skip the middleman entirely! By default, Serverless just orchestrates starting up services in your cloud provider of choice and pushing your code up to them. Any bill you receive is from your hosting provider (although many offer a generous free tier). You can optionally use the [Serverless Dashboard](https://www.serverless.com/dashboard/) to monitor your deploys and setup CI/CD to automatically deploy when pushing to your repo of choice. If you don't setup CI/CD you actually deploy from your development machine (or another designated machine you've setup to do the deployment).
Expand Down
282 changes: 282 additions & 0 deletions packages/cli/src/commands/deploy/serverless.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
import fs from 'fs'
import path from 'path'

import boxen from 'boxen'
import chalk from 'chalk'
import { config } from 'dotenv-defaults'
import execa from 'execa'
import { Listr } from 'listr2'
import prompts from 'prompts'
import terminalLink from 'terminal-link'

import { getPaths } from '../../lib'
import c from '../../lib/colors'

export const command = 'serverless'
export const aliases = ['aws serverless', 'sls']
export const description = 'Deploy to AWS via the serverless framework'

export const builder = (yargs) => {
yargs.option('stage', {
describe:
'serverless stage pass through param: https://www.serverless.com/blog/stages-and-environments',
default: 'production',
type: 'string',
})

yargs.option('sides', {
describe: 'which Side(s) to deploy',
choices: ['api', 'web'],
default: ['api', 'web'],
alias: 'side',
type: 'array',
})

yargs.option('verbose', {
describe: 'verbosity of logs',
default: true,
type: 'boolean',
})

yargs.option('pack-only', {
describe: 'Only build and pack, and dont push code up using serverless',
default: false,
type: 'boolean',
})

yargs.option('first-run', {
describe:
'Set this flag the first time you deploy, to configure your API URL on the webside',
default: false,
type: 'boolean',
})

yargs.epilogue(
`Also see the ${terminalLink(
'Redwood CLI Reference',
'https://redwoodjs.com/docs/cli-commands#deploy'
)}\n`
)
}

export const preRequisites = () => [
{
title: 'Checking if Serverless framework is installed...',
command: ['yarn serverless', ['--version']],
errorMessage: [
'Looks like Serverless is not installed.',
'Please run yarn add -W --dev serverless.',
],
},
]

export const buildCommands = ({ sides }) => {
return [
{
title: `Building ${sides.join(' & ')}...`,
command: ['yarn', ['rw', 'build', ...sides]],
},
{
title: 'Packing Functions...',
enabled: () => sides.includes('api'),
task: async () => {
// Dynamically import this function
// because its dependencies are only installed when `rw setup deploy serverless` is run
const { nftPack } = (await import('./packing/nft')).default

await nftPack()
},
},
]
}

export const deployCommands = ({ stage, sides, firstRun, packOnly }) => {
const slsStage = stage ? ['--stage', stage] : []

return sides.map((side) => {
return {
title: `Deploying ${side}....`,
task: async () => {
await execa('yarn', ['serverless', 'deploy', ...slsStage], {
cwd: path.join(getPaths().base, side),
shell: true,
stdio: 'inherit',
cleanup: true,
})
},
skip: () => {
if (firstRun && side === 'web') {
return 'Skipping web deploy, until environment configured'
}

if (packOnly) {
return 'Finishing early due to --pack-only flag. Your Redwood project is packaged and ready to deploy'
}
},
}
})
}

const loadDotEnvForStage = (dotEnvPath) => {
// Make sure we use the correct .env based on the stage
config({
path: dotEnvPath,
defaults: path.join(getPaths().base, '.env.defaults'),
encoding: 'utf8',
})
}

export const handler = async (yargs) => {
const rwjsPaths = getPaths()
const dotEnvPath = path.join(rwjsPaths.base, `.env.${yargs.stage}`)

// Make sure .env.staging, .env.production, etc are loaded based on the --stage flag
loadDotEnvForStage(dotEnvPath)

const tasks = new Listr(
[
...preRequisites(yargs).map(mapCommandsToListr),
...buildCommands(yargs).map(mapCommandsToListr),
...deployCommands(yargs).map(mapCommandsToListr),
],
{
exitOnError: true,
renderer: yargs.verbose && 'verbose',
}
)
try {
await tasks.run()

if (yargs.firstRun) {
const SETUP_MARKER = chalk.bgBlue(chalk.black('First Setup '))
console.log()

console.log(SETUP_MARKER, c.green('Starting first setup wizard...'))

const { stdout: slsInfo } = await execa(
`yarn serverless info --verbose --stage=${yargs.stage}`,
{
shell: true,
cwd: getPaths().api.base,
}
)

const deployedApiUrl = slsInfo.match(/HttpApiUrl: (https:\/\/.*)/)[1]

console.log()
console.log(SETUP_MARKER, `Found ${c.green(deployedApiUrl)}`)
console.log()

const { addDotEnv } = await prompts({
type: 'confirm',
name: 'addDotEnv',
message: `Add API_URL to your .env.${yargs.stage}? This will be used if you deploy the web side from your machine`,
})

if (addDotEnv) {
fs.writeFileSync(dotEnvPath, `API_URL=${deployedApiUrl}`)

// Reload dotenv, after adding the new file
loadDotEnvForStage(dotEnvPath)
}

if (yargs.sides.includes('web')) {
console.log()
console.log(SETUP_MARKER, 'Deploying web side with updated API_URL')

console.log(
SETUP_MARKER,
'First deploys can take a good few minutes...'
)
console.log()

const webDeployTasks = new Listr(
[
// Rebuild web with the new API_URL
...buildCommands({ ...yargs, sides: ['web'], firstRun: false }).map(
mapCommandsToListr
),
...deployCommands({
...yargs,
sides: ['web'],
firstRun: false,
}).map(mapCommandsToListr),
],
{
exitOnError: true,
renderer: yargs.verbose && 'verbose',
}
)

// Deploy the web side now that the API_URL has been configured
await webDeployTasks.run()

const { stdout: slsInfo } = await execa(
`yarn serverless info --verbose --stage=${yargs.stage}`,
{
shell: true,
cwd: getPaths().web.base,
}
)

const deployedWebUrl = slsInfo.match(/url: (https:\/\/.*)/)[1]

const message = [
c.bold('Successful first deploy!'),
'',
`View your deployed site at: ${c.green(deployedWebUrl)}`,
'',
'You can use serverless.com CI/CD by connecting/creating an app',
'To do this run `yarn serverless` on each of the sides, and connect your account',
'',
'Find more information in our docs:',
c.underline('https://redwoodjs.com/docs/deploy#serverless'),
]

console.log(
boxen(message.join('\n'), {
padding: { top: 0, bottom: 0, right: 1, left: 1 },
margin: 1,
borderColor: 'gray',
})
)
}
}
} catch (e) {
console.error(c.error(e.message))
process.exit(e?.exitCode || 1)
}
}

const mapCommandsToListr = ({
title,
command,
task,
cwd,
errorMessage,
skip,
enabled,
}) => {
return {
title,
task: task
? task
: async () => {
try {
const executingCommand = execa(...command, {
cwd: cwd || getPaths().base,
shell: true,
})
executingCommand.stdout.pipe(process.stdout)
await executingCommand
} catch (error) {
if (errorMessage) {
error.message = error.message + '\n' + errorMessage.join(' ')
}
throw error
}
},
skip,
enabled,
}
}
Loading

0 comments on commit 37b4df3

Please sign in to comment.