Skip to content

Commit

Permalink
Command to start a local container (#491)
Browse files Browse the repository at this point in the history
* RFC local conatiner commands

* Log progress

* Use stderrStream

* Remove -q

* No jumps when writing output

* Fix build

* Improvements for states

* Marke awaits in loop OK

* Refactor

* Refactor

* Refactor

* Initial tests

* Initial tests

* Initial tests

* Fix stuff

* More tests

* More tests

* Move test file to be for local command. Remove ability to pick the image

* Apply suggestions from code review

Co-authored-by: echo-bravo-yahoo <[email protected]>

---------

Co-authored-by: echo-bravo-yahoo <[email protected]>
  • Loading branch information
cleve-fauna and echo-bravo-yahoo authored Dec 12, 2024
1 parent 02b5166 commit 1801770
Show file tree
Hide file tree
Showing 12 changed files with 898 additions and 16 deletions.
260 changes: 256 additions & 4 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@shikijs/cli": "^1.24.0",
"awilix": "^12.0.2",
"chalk": "^5.3.0",
"dockerode": "^4.0.2",
"eslint": "^9.12.0",
"esprima": "^4.0.1",
"fauna": "^2.4.0",
Expand Down Expand Up @@ -79,7 +80,7 @@
"pretest:ci": "npm run build:app",
"test:ci": "mocha --forbid-only --recursive ./test --require ./test/mocha-root-hooks.mjs --reporter mocha-multi-reporters --reporter-options configFile=./test/config/reporter.json",
"build": "npm run build:app && npm run build:sea",
"build:app": "esbuild --bundle ./src/user-entrypoint.mjs --platform=node --outfile=./dist/cli.cjs --format=cjs --inject:./sea/import-meta-url.js --define:import.meta.url=importMetaUrl --define:process.env.NODE_ENV=\\\"production\\\"",
"build:app": "esbuild --loader:.node=file --bundle ./src/user-entrypoint.mjs --platform=node --outfile=./dist/cli.cjs --format=cjs --inject:./sea/import-meta-url.js --define:import.meta.url=importMetaUrl --define:process.env.NODE_ENV=\\\"production\\\"",
"build:sea": "node ./sea/build.cjs",
"format": "prettier -w --log-level silent .",
"format:check": "prettier -c .",
Expand Down
2 changes: 2 additions & 0 deletions src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chalk from "chalk";
import yargs from "yargs";

import databaseCommand from "./commands/database/database.mjs";
import localCommand from "./commands/local.mjs";
import loginCommand from "./commands/login.mjs";
import queryCommand from "./commands/query.mjs";
import schemaCommand from "./commands/schema/schema.mjs";
Expand Down Expand Up @@ -108,6 +109,7 @@ function buildYargs(argvInput) {
.command(loginCommand)
.command(schemaCommand)
.command(databaseCommand)
.command(localCommand)
.demandCommand()
.strictCommands(true)
.options({
Expand Down
55 changes: 55 additions & 0 deletions src/commands/local.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ensureContainerRunning } from "../lib/docker-containers.mjs";

/**
* Starts the local Fauna container
* @param {import('yargs').Arguments} argv The arguments from yargs
* @returns {Promise<void>} a promise that resolves when the container is ready.
* It will reject if the container is not ready after the maximum number of attempts.
*/
async function startLocal(argv) {
await ensureContainerRunning({
imageName: argv.image,
containerName: argv.name,
hostPort: argv.hostPort,
containerPort: argv.containerPort,
pull: argv.pull,
});
}

/**
* Builds the yargs command for the local command
* @param {import('yargs').Argv} yargs The yargs instance
* @returns {import('yargs').Argv} The yargs instance
*/
function buildLocalCommand(yargs) {
return yargs.options({
containerPort: {
describe: "The port inside the container Fauna listens on.",
type: "number",
default: "8443",
},
hostPort: {
describe:
"The port on the host machine mapped to the container's port. This is the port you'll connect to Fauna on.",
type: "number",
default: "8443",
},
name: {
describe: "The name to give the container",
type: "string",
default: "faunadb",
},
pull: {
describe: "Pull the latest image before starting the container.",
type: "boolean",
default: true,
},
});
}

export default {
command: "local",
describe: "Start a local Fauna container",
builder: buildLocalCommand,
handler: startLocal,
};
9 changes: 9 additions & 0 deletions src/config/setup-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { exit } from "node:process";
import { confirm } from "@inquirer/prompts";
import * as awilix from "awilix";
import { Lifetime } from "awilix";
import Docker from "dockerode";
import fauna from "fauna";
import faunadb from "faunadb";
import open from "open";
Expand Down Expand Up @@ -74,6 +75,14 @@ export const injectables = {
// generic lib (homemade utilities)
parseYargs: awilix.asValue(parseYargs),
logger: awilix.asFunction(buildLogger, { lifetime: Lifetime.SINGLETON }),
docker: awilix.asFunction(
() => {
const dockerInstance = new Docker(); // Create instance
// If Docker requires additional async setup, perform it here and return a promise
return dockerInstance;
},
{ lifetime: Lifetime.SINGLETON },
),
oauthClient: awilix.asClass(OAuthClient, { lifetime: Lifetime.SCOPED }),
makeAccountRequest: awilix.asValue(makeAccountRequest),
makeFaunaRequest: awilix.asValue(makeRetryableFaunaRequest),
Expand Down
9 changes: 9 additions & 0 deletions src/config/setup-test-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ export function setupTestContainer() {
getSession: stub(),
})),
oauthClient: awilix.asFunction(stub()),
docker: awilix.asValue({
createContainer: stub(),
getContainer: stub(),
listContainers: stub(),
modem: {
followProgress: stub(),
},
pull: stub(),
}),
credentials: awilix.asClass(stub()).singleton(),
errorHandler: awilix.asValue((error, exitCode) => {
error.code = exitCode;
Expand Down
10 changes: 5 additions & 5 deletions src/lib/auth/oauth-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { container } from "../../cli.mjs";
import SuccessPage from "./successPage.mjs";

// Default to prod client id and secret
const clientId = process.env.FAUNA_CLIENT_ID ?? "Aq4_G0mOtm_F1fK3PuzE0k-i9F0";
const CLIENT_ID = process.env.FAUNA_CLIENT_ID ?? "Aq4_G0mOtm_F1fK3PuzE0k-i9F0";
// Native public clients are not confidential. The client secret is not used beyond
// client identification. https://datatracker.ietf.org/doc/html/rfc8252#section-8.5
const clientSecret =
const CLIENT_SECRET =
process.env.FAUNA_CLIENT_SECRET ??
"2W9eZYlyN5XwnpvaP3AwOfclrtAjTXncH6k-bdFq1ZV0hZMFPzRIfg";
const REDIRECT_URI = `http://127.0.0.1`;
Expand All @@ -28,7 +28,7 @@ class OAuthClient {

getOAuthParams() {
return {
client_id: clientId, // eslint-disable-line camelcase
client_id: CLIENT_ID, // eslint-disable-line camelcase
redirect_uri: `${REDIRECT_URI}:${this.port}`, // eslint-disable-line camelcase
code_challenge: this.codeChallenge, // eslint-disable-line camelcase
code_challenge_method: "S256", // eslint-disable-line camelcase
Expand All @@ -40,8 +40,8 @@ class OAuthClient {

getTokenParams() {
return {
clientId,
clientSecret,
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
authCode: this.authCode,
redirectURI: `${REDIRECT_URI}:${this.port}`,
codeVerifier: this.codeVerifier,
Expand Down
Loading

0 comments on commit 1801770

Please sign in to comment.