Skip to content

Prototype Tool to Create Cloud Run Apps with Firebase CLI - #10898

Open
falahat wants to merge 22 commits into
mainfrom
bapi_prototype
Open

Prototype Tool to Create Cloud Run Apps with Firebase CLI#10898
falahat wants to merge 22 commits into
mainfrom
bapi_prototype

Conversation

@falahat

@falahat falahat commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This initial draft was ai-generated and must still be reviewed carefully by humans. It has been tested manually:

Description

Scenarios Tested

Sample Commands

firebase init run
firebase deploy --only run --project xxx

…veiwed by humans and brought up to bar.

Testing: This was tested manually by deploying a Cloud Run app
@wiz-9635d3485b

wiz-9635d3485b Bot commented Aug 6, 2026

Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings 5 Medium 16 Low
Software Management Finding Software Management Findings -
Total 5 Medium 16 Low

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for configuring and deploying Cloud Run services via the Firebase CLI, including integration with App Hosting configurations, initialization features, and end-to-end tests. The feedback highlights several critical issues: hardcoded absolute paths in the E2E test script, a bug in constructing the secret resource path for Cloud Run environment variables, and a violation of best practices regarding revision-level versus service-level scaling. Additionally, the modification to updateService in src/gcp/runv2.ts is flagged as highly risky for existing Cloud Functions v2 deployments. Finally, the reviewer recommends removing an accidentally committed backup file (deploy.ts.bak), adding validation for serviceId, and adhering to the repository style guide by throwing FirebaseError instead of generic Error objects.

Comment thread e2e_run_tests/run_all_tests.sh Outdated
Comment thread src/deploy/run/deploy.ts Outdated
Comment thread src/deploy/run/deploy.ts Outdated
Comment thread src/gcp/runv2.ts Outdated
Comment thread src/deploy/run/deploy.ts.bak Outdated
Comment thread src/deploy/run/prepare.ts Outdated
Comment thread src/deploy/run/prepare.ts
Comment thread src/init/features/run.ts Outdated
Comment thread src/init/features/run.ts
falahat added 7 commits August 7, 2026 10:33
Do Not read apphosting.local.yaml by accident
Add timeout to artifact registry actions
Track Cloud Build operations/results better
* Deduplicated Test CLI Process Wrapper
* Standardized Secret Name Parsing
* Standardized GCP API Verification
* Resolved RunConfig Type Naming Collisions
* Materialized Target Configuration & Target Filtering
* Updated firebase.json schema to include the "run" section
* Cleaned Init Feature Scaffolding
* Gated ABIU Base Image Updates
@falahat

falahat commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for deploying Cloud Run services via the Firebase CLI, adding configuration schemas, initialization prompts, deployment lifecycle hooks, and an E2E test suite. The code reviewer provided valuable feedback focused on aligning the implementation with repository style guides. Key recommendations include replacing manual polling loops with the central pollOperation utility, avoiding the use of the :latest tag for container images to prevent deployment overwrites, eliminating as any type casts by properly typing options and configurations, and ensuring FirebaseError throws specify non-zero exit codes for precondition violations.

Comment thread src/gcp/runv2.ts
Comment thread src/deploy/run/deploy.ts Outdated
Comment thread src/deploy/run/deploy.ts Outdated
Comment thread src/deploy/run/deploy.ts Outdated
Comment thread src/deploy/run/prepare.ts Outdated
Comment thread src/init/features/run.ts
Comment thread src/deploy/run/prepare.ts Outdated
Comment thread src/commands/init.ts
Comment on lines +165 to 172
.option("-s, --service <serviceId>", "Cloud Run service ID")
.option("--service-id <serviceId>", "Cloud Run service ID")
.option("--primary-region <region>", "primary region for Cloud Run")
.option("--region <region>", "region for Cloud Run")
.option("--root-dir <rootDir>", "root directory for source code")
.option("--output-dir <outputDir>", "output directory for built artifacts")
.help(HELP)
.action(initAction);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldnt add the cloud-run specific command args to the init command for ALL features. These flags are only relevant if running firebase init run, is there any way we can clean this up.

Comment thread src/commands/init.ts
Comment on lines -222 to 285
// HACK: Windows Node has issues with selectables as the first prompt, so we
// add an extra confirmation prompt that fixes the problem
// TODO: see if this issue still persists in the new prompt library.
if (process.platform === "win32") {
if (!(await confirm("Are you ready to proceed?"))) {
throw new FirebaseError("Aborted by user.", { exit: 1 });
}
}

if (feature) {
setup.featureArg = true;
setup.features = [feature];
} else {
setup.features = await checkbox<string>({
message:
"Which Firebase features do you want to set up for this directory? " +
"Press Space to select features, then Enter to confirm your choices.",
choices: choices.filter((c) => !c.hidden),
validate: (choices) => {
if (choices.length === 0) {
return (
"Must select at least one feature. Use " +
clc.bold(clc.underline("SPACEBAR")) +
" to select features, or specify a feature by running " +
clc.bold("firebase init [feature_name]")
);
}
return true;
},
});
}
if (!setup.features || setup.features?.length === 0) {
throw new FirebaseError(
"Must select at least one feature. Use " +
clc.bold(clc.underline("SPACEBAR")) +
" to select features, or specify a feature by running " +
clc.bold("firebase init [feature_name]"),
);
}

// Always set up project
setup.features.unshift("project");

// If there is more than one account, add an account choice phase
const allAccounts = getAllAccounts();
if (allAccounts.length > 1) {
setup.features.unshift("account");
}

// "hosting:github" is a part of "hosting", so if both are selected, "hosting:github" is ignored.
if (setup.features.includes("hosting") && setup.features.includes("hosting:github")) {
setup.features = setup.features.filter((f) => f !== "hosting:github");
}
// "dataconnect:sdk" is a part of "dataconnect", so if both are selected, "dataconnect:sdk" is ignored.
if (setup.features.includes("dataconnect") && setup.features.includes("dataconnect:sdk")) {
setup.features = setup.features.filter((f) => f !== "dataconnect:sdk");
}

// Always prompt for agent skills at the end of init
setup.features.push("agentSkills");

await init(setup, config, options);
await postInitSaves(setup, config);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting up the init function is good, and it can be done in a pre-requisite PR first.

Basically, I'll just extract this out for readability / faster reviw

Comment thread src/deploy/run/args.ts
Comment on lines +6 to +16
export const DEFAULT_RUN_IGNORE = [
"node_modules",
".git",
".next",
".run",
"firebase-debug.log",
"firebase-debug.*.log",
".env*.local",
"apphosting.local.yaml",
"**/*.secret.local",
];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrutinize these, why should we be this opinionated?

/**
* Ensures an Artifact Registry repository exists, creating it if not.
*/
export async function ensureRepository(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to ensureRepositoryExists

@falahat

falahat commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for configuring and deploying Cloud Run services directly via the Firebase CLI, including integration with apphosting.yaml settings such as scaling, VPC access, and environment variables. Feedback on the changes highlights three critical issues: defaulting supportGitIgnore to true in archiveDirectory is a breaking change that could affect other deploy targets; the lifecycle hook filtering for run will incorrectly reject configurations when deploying with --only run; and polling Cloud Build by extracting a buildId is fragile and should be replaced by polling the buildOperation resource directly.

Comment thread src/archiveDirectory.ts
path: sourceDirectory,
ignoreStrings: options.ignore,
ignoreSymlinks: true,
supportGitIgnore: options.supportGitIgnore ?? true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Changing the default of supportGitIgnore to true for all callers of archiveDirectory is a breaking change. Other deploy targets (like Functions or Hosting) that use archiveDirectory might not expect .gitignore to be automatically respected, which could silently exclude files from their deployments. It is safer to default this to false to preserve backward compatibility, while allowing Cloud Run to explicitly opt-in.

Suggested change
supportGitIgnore: options.supportGitIgnore ?? true,
supportGitIgnore: options.supportGitIgnore ?? false,

Comment on lines +181 to +184
} else if (target === "run") {
return targetConfigs.filter((config: any) => {
return !config.serviceId || onlyTargets.includes(config.serviceId);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the user runs firebase deploy --only run, onlyTargets will contain ["run"] (or similar depending on parsing, but it won't contain the specific serviceId). Since config.serviceId is always defined, !config.serviceId is false, and onlyTargets.includes(config.serviceId) is false. Thus, the filter will reject all configs, and none of the lifecycle hooks (predeploy/postdeploy) for run will execute. We should also check if the general target "run" is in the list of only targets.

Suggested change
} else if (target === "run") {
return targetConfigs.filter((config: any) => {
return !config.serviceId || onlyTargets.includes(config.serviceId);
});
} else if (target === "run") {
return targetConfigs.filter((config: any) => {
return !config.serviceId || onlyTargets.includes(config.serviceId) || onlyTargets.includes("run");
});

Comment thread src/gcp/runv2.ts
Comment on lines +205 to +248
const op = res.body.buildOperation;
const buildId =
(typeof op === "object" && op?.metadata?.build?.id) ||
(typeof op === "string"
? op
.split("/")
.pop()
?.replace(/^build-/, "")
: "");
if (buildId) {
let latestBuild: { status?: string; statusDetail?: string; logUrl?: string } | undefined;
await pollOperation<any>({
pollerName: "Cloud Build Poller",
apiOrigin: cloudbuildOrigin(),
apiVersion: "v1",
operationResourceName: `projects/${projectId}/locations/${location}/builds/${buildId}`,
masterTimeout: 15 * 60 * 1000,
backoff: 2000,
maxBackoff: 10000,
onPoll: (res: any) => {
latestBuild = res;
},
doneFn: (buildRes: any) => {
const status = buildRes?.status;
return (
status === "SUCCESS" ||
status === "FAILURE" ||
status === "INTERNAL_ERROR" ||
status === "TIMEOUT" ||
status === "CANCELLED"
);
},
});

if (latestBuild && latestBuild.status !== "SUCCESS") {
const detail = latestBuild.statusDetail ? `: ${latestBuild.statusDetail}` : "";
const consoleLink =
latestBuild.logUrl ||
`https://console.cloud.google.com/cloud-build/builds;region=${location}/${buildId}?project=${projectId}`;
throw new FirebaseError(
`Cloud Build failed with status ${latestBuild.status}${detail}\nView Cloud Build logs at: ${consoleLink}`,
);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Polling builds/${buildId} directly is extremely fragile and prone to 404 errors if buildOperation is returned as a string that doesn't match the expected build- prefix format. Instead of extracting the buildId and polling the Cloud Build Build resource, we should poll the buildOperation resource name directly (which is guaranteed to be valid). The operation's metadata contains the build object, so we can still access the build status, status detail, and log URL from op.metadata.build on each poll.

  const op = res.body.buildOperation;
  const operationResourceName = typeof op === "string" ? op : op.name;
  if (operationResourceName) {
    let latestBuild: { status?: string; statusDetail?: string; logUrl?: string } | undefined;
    await pollOperation<any>({
      pollerName: "Cloud Build Poller",
      apiOrigin: cloudbuildOrigin(),
      apiVersion: "v1",
      operationResourceName,
      masterTimeout: 15 * 60 * 1000,
      backoff: 2000,
      maxBackoff: 10000,
      onPoll: (opRes: any) => {
        latestBuild = opRes?.metadata?.build;
      },
      doneFn: (opRes: any) => {
        const status = opRes?.metadata?.build?.status;
        return (
          status === "SUCCESS" ||
          status === "FAILURE" ||
          status === "INTERNAL_ERROR" ||
          status === "TIMEOUT" ||
          status === "CANCELLED"
        );
      },
    });

    if (latestBuild && latestBuild.status !== "SUCCESS") {
      const detail = latestBuild.statusDetail ? `: ${latestBuild.statusDetail}` : "";
      const consoleLink =
        latestBuild.logUrl ||
        `https://console.cloud.google.com/cloud-build/builds?project=${projectId}`;
      throw new FirebaseError(
        `Cloud Build failed with status ${latestBuild.status}${detail}\nView Cloud Build logs at: ${consoleLink}`,
      );
    }
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants