Adds --org and launch secrets to wasp deploy (#1196)

This commit is contained in:
Shayne Czyzewski 2023-05-23 10:25:37 -04:00 committed by GitHub
parent d5b9d78542
commit 8d5530519b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 99 additions and 28 deletions

View File

@ -5,6 +5,9 @@
### Bug fixes
- `wasp deploy fly launch` now supports the latest `flyctl launch` toml file for the web client (which changed their default structure and port).
### More `wasp deploy fly` options
`wasp deploy fly` now supports a `--org` option, as well as setting secrets during `launch`.
## v0.10.5
### Bug fixes

View File

@ -2,6 +2,7 @@ export interface CommonOptions {
waspExe: string;
waspProjectDir: string;
flyTomlDir?: string;
org?: string;
}
export interface DbOptions {
@ -13,3 +14,8 @@ export interface DbOptions {
export interface LocalBuildOptions {
buildLocally: boolean;
}
export interface SecretsOptions {
serverSecret: string[];
clientSecret: string[];
}

View File

@ -1,10 +1,10 @@
import { TomlFilePaths } from './helpers/tomlFileHelpers.js';
import { CommonOptions } from './CommonOptions.js';
export type DeploymentInfo = Readonly<{
export type DeploymentInfo<CommandOptions extends CommonOptions> = Readonly<{
baseName: string;
region?: string;
options: CommonOptions;
options: CommandOptions;
tomlFilePaths: TomlFilePaths;
clientName: string;
clientUrl: string;
@ -13,13 +13,13 @@ export type DeploymentInfo = Readonly<{
dbName: string;
}>;
export function createDeploymentInfo(
export function createDeploymentInfo<CommandOptions extends CommonOptions>(
baseName: string,
region: string | undefined,
options: CommonOptions,
options: CommandOptions,
tomlFilePaths: TomlFilePaths,
): DeploymentInfo {
return {
): DeploymentInfo<CommandOptions> {
return Object.freeze({
baseName,
region,
options,
@ -29,5 +29,5 @@ export function createDeploymentInfo(
serverName: `${baseName}-server`,
serverUrl: `https://${baseName}-server.fly.dev`,
dbName: `${baseName}-db`,
};
});
}

View File

@ -1,14 +1,17 @@
import { exit } from 'process';
import { $, cd } from 'zx';
import { CommonOps, getCommonOps } from '../helpers/CommonOps.js';
import { buildDirExists, getCommandHelp, waspSays } from '../helpers/helpers.js';
import { buildDirExists, waspSays } from '../helpers/helpers.js';
import { deleteLocalToml, getTomlFilePaths, localTomlExists } from '../helpers/tomlFileHelpers.js';
import { executeFlyCommand } from '../index.js';
import { CmdOptions } from './CmdOptions.js';
// Runs a command by copying down the project toml files, executing it, and copying it back up (just in case).
// If the toml file does not exist, some commands will not run with additional args (e.g. -a <appname>).
export async function cmd(flyctlArgs: string[], options: CmdOptions): Promise<void> {
if (options.org) {
flyctlArgs.push('--org', options.org);
}
waspSays(`Running ${options.context} command: flyctl ${flyctlArgs.join(' ')}`);
if (!buildDirExists(options.waspProjectDir)) {
@ -23,7 +26,7 @@ export async function cmd(flyctlArgs: string[], options: CmdOptions): Promise<vo
await runFlyctlCommand(commonOps, flyctlArgs);
}
async function runFlyctlCommand(commonOps: CommonOps, flyctlArgs: string[]) {
async function runFlyctlCommand(commonOps: CommonOps, flyctlArgs: string[]): Promise<void> {
commonOps.cdToBuildDir();
deleteLocalToml();
if (commonOps.tomlExistsInProject()) {
@ -33,8 +36,6 @@ async function runFlyctlCommand(commonOps: CommonOps, flyctlArgs: string[]) {
try {
await $`flyctl ${flyctlArgs}`;
} catch {
waspSays('Error running command. Note: many commands require a toml file or a -a option specifying the app name.');
waspSays(`If you already have an app, consider running "${getCommandHelp(executeFlyCommand).replace('<cmd...>', 'config save -- -a <app-name>')}".`);
exit(1);
}

View File

@ -28,6 +28,11 @@ export async function createDb(region: string, options: CreateDbOptions): Promis
'--initial-cluster-size', options.initialClusterSize,
'--volume-size', options.volumeSize,
];
if (deploymentInfo.options.org) {
createArgs.push('--org', deploymentInfo.options.org);
}
await $`flyctl postgres create ${createArgs}`;
await $`flyctl postgres attach ${deploymentInfo.dbName} -a ${deploymentInfo.serverName}`;

View File

@ -65,7 +65,7 @@ export async function deploy(options: DeployOptions): Promise<void> {
}
}
async function deployServer(deploymentInfo: DeploymentInfo, { buildLocally }: DeployOptions) {
async function deployServer(deploymentInfo: DeploymentInfo<DeployOptions>, { buildLocally }: DeployOptions) {
waspSays('Deploying your server now...');
cdToServerBuildDir(deploymentInfo.options.waspProjectDir);
@ -79,7 +79,6 @@ async function deployServer(deploymentInfo: DeploymentInfo, { buildLocally }: De
exit(1);
}
} catch (e) {
console.error(e);
waspSays('Unable to check for DATABASE_URL secret.');
exit(1);
}
@ -97,7 +96,7 @@ async function deployServer(deploymentInfo: DeploymentInfo, { buildLocally }: De
waspSays('Server has been deployed!');
}
async function deployClient(deploymentInfo: DeploymentInfo, { buildLocally }: DeployOptions) {
async function deployClient(deploymentInfo: DeploymentInfo<DeployOptions>, { buildLocally }: DeployOptions) {
waspSays('Deploying your client now...');
cdToClientBuildDir(deploymentInfo.options.waspProjectDir);

View File

@ -62,7 +62,6 @@ export async function ensureRegionIsValid(region: string): Promise<void> {
}
} catch (e) {
// Ignore any errors while checking. Commands requiring a valid region will still fail if invalid, just not as nicely.
console.error(e);
waspSays('Unable to validate region before calling flyctl.');
}
}

View File

@ -23,6 +23,13 @@ class FlyCommand extends Command {
addLocalBuildOption(): this {
return this.option('--build-locally', 'build Docker containers locally instead of remotely', false);
}
addSecretsOptions(): this {
function collect(value: string, previous: string[]) {
return previous.concat([value]);
}
return this.option('--server-secret <serverSecret>', 'secret to set on the server app (of form FOO=BAR)', collect, [])
.option('--client-secret <clientSecret>', 'secret to set on the client app (of form FOO=BAR)', collect, []);
}
}
const flyLaunchCommand = makeFlyLaunchCommand();
@ -53,6 +60,7 @@ export function addFlyCommand(program: Command): void {
cmd.addOption(new Option('--wasp-exe <path>', 'Wasp executable (either on PATH or absolute path)').hideHelp().makeOptionMandatory())
.addOption(new Option('--wasp-project-dir <dir>', 'absolute path to Wasp project dir').hideHelp().makeOptionMandatory())
.option('--fly-toml-dir <dir>', 'absolute path to dir where fly.toml files live')
.option('--org <org>', 'Fly org to use (with commands that support it)')
.hook('preAction', ensureFlyReady)
.hook('preAction', ensureDirsInCmdAreAbsoluteAndPresent)
.hook('preAction', ensureWaspDirLooksRight);
@ -71,6 +79,7 @@ function makeFlyLaunchCommand(): Command {
.addRegionArgument()
.addDbOptions()
.addLocalBuildOption()
.addSecretsOptions()
.action(launchFn);
}
@ -79,6 +88,7 @@ function makeFlySetupCommand(): Command {
.description('Set up a new app on Fly.io (this does not deploy it)')
.addBasenameArgument()
.addRegionArgument()
.addSecretsOptions()
.action(setupFn);
}

View File

@ -1,3 +1,3 @@
import { CommonOptions, DbOptions, LocalBuildOptions } from '../CommonOptions.js';
import { CommonOptions, DbOptions, LocalBuildOptions, SecretsOptions } from '../CommonOptions.js';
export interface LaunchOptions extends CommonOptions, DbOptions, LocalBuildOptions {}
export interface LaunchOptions extends CommonOptions, DbOptions, LocalBuildOptions, SecretsOptions { }

View File

@ -20,7 +20,6 @@ export async function launch(basename: string, region: string, options: LaunchOp
try {
await setup(basename, region, options);
} catch (e) {
console.error(e);
waspSays(`There was an error running "${getCommandHelp(flySetupCommand)}". Please review the error and try again (if appropriate).`);
exit(1);
}
@ -28,7 +27,6 @@ export async function launch(basename: string, region: string, options: LaunchOp
try {
await createDb(region, options);
} catch (e) {
console.error(e);
waspSays(`There was an error running "${getCommandHelp(createFlyDbCommand)}". Please review the error and try again (if appropriate).`);
exit(1);
}
@ -37,7 +35,6 @@ export async function launch(basename: string, region: string, options: LaunchOp
const deployOptions: DeployOptions = { ...options, skipBuild: true };
await deploy(deployOptions);
} catch (e) {
console.error(e);
waspSays(`There was an error running "${getCommandHelp(flyDeployCommand)}". Please review the error and try again (if appropriate).`);
exit(1);
}

View File

@ -0,0 +1,3 @@
import { CommonOptions, SecretsOptions } from '../CommonOptions.js';
export interface SetupOptions extends CommonOptions, SecretsOptions { }

View File

@ -10,11 +10,11 @@ import {
serverTomlExistsInProject,
} from '../helpers/tomlFileHelpers.js';
import { createDeploymentInfo, DeploymentInfo } from '../DeploymentInfo.js';
import { CommonOptions } from '../CommonOptions.js';
import { SetupOptions } from './SetupOptions.js';
import { cdToClientBuildDir, cdToServerBuildDir, makeIdempotent, getCommandHelp, waspSays } from '../helpers/helpers.js';
import { createFlyDbCommand } from '../index.js';
export async function setup(baseName: string, region: string, options: CommonOptions): Promise<void> {
export async function setup(baseName: string, region: string, options: SetupOptions): Promise<void> {
waspSays('Setting up your Wasp app with Fly.io!');
const buildWasp = makeIdempotent(async () => {
@ -43,32 +43,65 @@ export async function setup(baseName: string, region: string, options: CommonOpt
waspSays(`Don't forget to create your database by running "${getCommandHelp(createFlyDbCommand)}".`);
}
async function setupServer(deploymentInfo: DeploymentInfo) {
async function setupServer(deploymentInfo: DeploymentInfo<SetupOptions>) {
waspSays(`Setting up server app with name ${deploymentInfo.serverName}`);
cdToServerBuildDir(deploymentInfo.options.waspProjectDir);
deleteLocalToml();
const launchArgs = [
'--name', deploymentInfo.serverName,
'--region', deploymentInfo.region,
];
if (deploymentInfo.options.org) {
launchArgs.push('--org', deploymentInfo.options.org);
}
// This creates the fly.toml file, but does not attempt to deploy.
await $`flyctl launch --no-deploy --name ${deploymentInfo.serverName} --region ${deploymentInfo.region}`;
await $`flyctl launch --no-deploy ${launchArgs}`;
copyLocalServerTomlToProject(deploymentInfo.tomlFilePaths);
const randomString = crypto.randomBytes(32).toString('hex');
await $`flyctl secrets set JWT_SECRET=${randomString} PORT=8080 WASP_WEB_CLIENT_URL=${deploymentInfo.clientUrl}`;
const secretsArgs = [
`JWT_SECRET=${randomString}`,
// NOTE: Normally these would just be envars, but flyctl
// doesn't provide a way to set envars that persist to fly.toml.
'PORT=8080',
`WASP_WEB_CLIENT_URL=${deploymentInfo.clientUrl}`,
];
if (deploymentInfo.options.serverSecret.length > 0) {
deploymentInfo.options.serverSecret.forEach(secret => {
secretsArgs.push(secret);
});
}
await $`flyctl secrets set ${secretsArgs}`;
console.log(''); // `flyctl secrets` does not produce it's own newline.
waspSays('Server setup complete!');
}
async function setupClient(deploymentInfo: DeploymentInfo) {
async function setupClient(deploymentInfo: DeploymentInfo<SetupOptions>) {
waspSays(`Setting up client app with name ${deploymentInfo.clientName}`);
cdToClientBuildDir(deploymentInfo.options.waspProjectDir);
deleteLocalToml();
const launchArgs = [
'--name', deploymentInfo.clientName,
'--region', deploymentInfo.region,
];
if (deploymentInfo.options.org) {
launchArgs.push('--org', deploymentInfo.options.org);
}
// This creates the fly.toml file, but does not attempt to deploy.
await $`flyctl launch --no-deploy --name ${deploymentInfo.clientName} --region ${deploymentInfo.region}`;
await $`flyctl launch --no-deploy ${launchArgs}`;
// goStatic listens on port 8043 by default, but the default fly.toml
// assumes port 8080 (or 3000, depending on flyctl version).
@ -76,5 +109,9 @@ async function setupClient(deploymentInfo: DeploymentInfo) {
copyLocalClientTomlToProject(deploymentInfo.tomlFilePaths);
if (deploymentInfo.options.clientSecret.length > 0) {
await $`flyctl secrets set ${deploymentInfo.options.clientSecret}`;
}
waspSays('Client setup complete!');
}

View File

@ -65,11 +65,22 @@ wasp deploy fly cmd secrets list --context server
:::note
If you are deploying an app that requires any other environment variables (like social auth secrets), you will want to set your environment variables up like so:
During `launch`:
```
wasp deploy fly launch my-wasp-app mia --server-secret GOOGLE_CLIENT_ID=<...> --server-secret GOOGLE_CLIENT_SECRET=<...>
```
After `launch`/`setup`:
```
wasp deploy fly cmd secrets set GOOGLE_CLIENT_ID=<...> GOOGLE_CLIENT_SECRET=<...> --context=server
```
:::
:::note
If you have multiple orgs, you can specify a `--org` option. For example: `wasp deploy fly launch my-wasp-app mia --org hive`
:::
# Manual
In addition to the CLI, you can deploy a Wasp project by generating the code and then deploying generated code "manually", as explained below.