Skip to content

Commit 269730a

Browse files
committed
Merge branch 'master' into hsinpei/database-chunk-4
2 parents f75178c + 1aa6160 commit 269730a

File tree

16 files changed

+278
-53
lines changed

16 files changed

+278
-53
lines changed

CHANGELOG.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,2 @@
1-
- Releases Firestore Emulator 1.17.1
2-
- Propagates page token from ListDocumentsResponse to GetOrListDocumentsResponse in Firestore emulator.
3-
- Fixes an issue where Secret Manager secrets were tagged incorrectly (#5704).
4-
- Fix bug where Custom Event channels weren't automatically crated on function deploys (#5700)
5-
- Lift GCF 2nd gen naming restrictions (#5690)
6-
- Fixes a bug where `ext:install` and `ext:configure` would error on extensions with no params.
7-
- Fixed an issue with Vite and Angular integrations using a obsolete NPM command (#5710)
1+
- Added more helpful error messages for the Firebase Hosting GitHub Action (#5749)
2+
- Upgrade Firestore emulator to 1.17.4

npm-shrinkwrap.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firebase-tools",
3-
"version": "11.28.0",
3+
"version": "11.29.1",
44
"description": "Command-Line Interface for Firebase",
55
"main": "./lib/index.js",
66
"bin": {

src/commands/hosting-channel-deploy.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ export const command = new Command("hosting:channel:deploy [channelId]")
8686
.split(",")
8787
.map((o: string) => `hosting:${o}`)
8888
.join(",");
89+
} else {
90+
// N.B. The hosting deploy code uses the only string to add all (and only)
91+
// functions that are pinned to the only string. If we didn't set the
92+
// only string here and only used the hosting deploy targets, we'd only
93+
// be able to deploy *all* functions.
94+
options.only = "hosting";
8995
}
9096

9197
const sites: ChannelInfo[] = hostingConfig(options).map((config) => {

src/deploy/extensions/secrets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ const secretsInSpec = (spec: ExtensionSpec): Param[] => {
5555
};
5656

5757
async function handleSecretsCreateInstance(i: DeploymentInstanceSpec, nonInteractive: boolean) {
58-
const extensionVersion = await getExtensionVersion(i);
59-
const secretParams = secretsInSpec(extensionVersion.spec);
58+
const spec = await getExtensionSpec(i);
59+
const secretParams = secretsInSpec(spec);
6060
for (const s of secretParams) {
6161
await handleSecretParamForCreate(s, i, nonInteractive);
6262
}

src/deploy/functions/prepare.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ import { generateServiceIdentity } from "../../gcp/serviceusage";
3636
import { applyBackendHashToBackends } from "./cache/applyHash";
3737
import { allEndpoints, Backend } from "./backend";
3838
import { assertExhaustive } from "../../functional";
39+
import { isRunningInGithubAction } from "../../init/features/hosting/github";
40+
import { firebaseRoles } from "../../gcp/resourceManager";
41+
import { requirePermissions } from "../../requirePermissions";
3942

4043
export const EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
4144
function hasUserConfig(config: Record<string, unknown>): boolean {
@@ -55,6 +58,15 @@ export async function prepare(
5558
const projectId = needProjectId(options);
5659
const projectNumber = await needProjectNumber(options);
5760

61+
// Fail early if we're running in our Github Action and don't have permission
62+
if (isRunningInGithubAction()) {
63+
await requirePermissions(options, [firebaseRoles.functionsDeveloper]).catch(() => {
64+
throw new FirebaseError(
65+
"Please reinstall the Github Action with `firebase init hosting:github` to grant this project the required permissions to deploy Cloud Functions."
66+
);
67+
});
68+
}
69+
5870
context.config = normalizeAndValidate(options.config.src.functions);
5971
context.filters = getEndpointFilters(options); // Parse --only filters for functions.
6072

src/deploy/hosting/prepare.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import * as deploymentTool from "../../deploymentTool";
55
import { Context } from "./context";
66
import { Options } from "../../options";
77
import { HostingOptions } from "../../hosting/options";
8-
import { zipIn } from "../../functional";
8+
import { assertExhaustive, zipIn } from "../../functional";
99
import { track } from "../../track";
10+
import * as utils from "../../utils";
11+
import { HostingSource } from "../../firebaseConfig";
12+
import * as backend from "../functions/backend";
1013

1114
/**
1215
* Prepare creates versions for each Hosting site to be deployed.
@@ -34,6 +37,12 @@ export async function prepare(context: Context, options: HostingOptions & Option
3437
if (config.webFramework) {
3538
labels["firebase-web-framework"] = config.webFramework;
3639
}
40+
const unsafe = await unsafePins(context, config);
41+
if (unsafe.length) {
42+
const msg = `Cannot deploy site ${config.site} to channel ${context.hostingChannel} because it would modify one or more rewrites in "live" that are not pinned, breaking production. Please pin "live" before pinning other channels.`;
43+
utils.logLabeledError("Hosting", msg);
44+
throw new Error(msg);
45+
}
3746
const version: Omit<api.Version, api.VERSION_OUTPUT_FIELDS> = {
3847
status: "CREATED",
3948
labels,
@@ -52,3 +61,73 @@ export async function prepare(context: Context, options: HostingOptions & Option
5261
context.hosting.deploys.push({ config, version });
5362
}
5463
}
64+
65+
function rewriteTarget(source: HostingSource): string {
66+
if ("glob" in source) {
67+
return source.glob;
68+
} else if ("source" in source) {
69+
return source.source;
70+
} else if ("regex" in source) {
71+
return source.regex;
72+
} else {
73+
assertExhaustive(source);
74+
}
75+
}
76+
77+
/**
78+
* Returns a list of rewrite targets that would break in prod if deployed.
79+
* People use tag pinning so that they can deploy to preview channels without
80+
* modifying production. This assumption is violated if the live channel isn't
81+
* actually pinned. This method returns "unsafe" pins, where we're deploying to
82+
* a non-live channel with a rewrite that is pinned but haven't yet pinned live.
83+
*/
84+
export async function unsafePins(
85+
context: Context,
86+
config: config.HostingResolved
87+
): Promise<string[]> {
88+
// Overwriting prod won't break prod
89+
if ((context.hostingChannel || "live") === "live") {
90+
return [];
91+
}
92+
93+
const targetTaggedRewrites: Record<string, string> = {};
94+
for (const rewrite of config.rewrites || []) {
95+
const target = rewriteTarget(rewrite);
96+
if ("run" in rewrite && rewrite.run.pinTag) {
97+
targetTaggedRewrites[target] = `${rewrite.run.region || "us-central1"}/${
98+
rewrite.run.serviceId
99+
}`;
100+
}
101+
if ("function" in rewrite && typeof rewrite.function === "object" && rewrite.function.pinTag) {
102+
const region = rewrite.function.region || "us-central1";
103+
const endpoint = (await backend.existingBackend(context)).endpoints[region]?.[
104+
rewrite.function.functionId
105+
];
106+
// This function is new. It can't be pinned elsewhere
107+
if (!endpoint) {
108+
continue;
109+
}
110+
targetTaggedRewrites[target] = `${region}/${endpoint.runServiceId || endpoint.id}`;
111+
}
112+
}
113+
114+
if (!Object.keys(targetTaggedRewrites).length) {
115+
return [];
116+
}
117+
118+
const channelConfig = await api.getChannel(context.projectId, config.site, "live");
119+
const existingUntaggedRewrites: Record<string, string> = {};
120+
for (const rewrite of channelConfig?.release?.version?.config?.rewrites || []) {
121+
if ("run" in rewrite && !rewrite.run.tag) {
122+
existingUntaggedRewrites[
123+
rewriteTarget(rewrite)
124+
] = `${rewrite.run.region}/${rewrite.run.serviceId}`;
125+
}
126+
}
127+
128+
// There is only a problem if we're targeting the same exact run service but
129+
// live isn't tagged.
130+
return Object.keys(targetTaggedRewrites).filter(
131+
(target) => targetTaggedRewrites[target] === existingUntaggedRewrites[target]
132+
);
133+
}

src/deploy/index.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import * as RemoteConfigTarget from "./remoteconfig";
1717
import * as ExtensionsTarget from "./extensions";
1818
import { prepareFrameworks } from "../frameworks";
1919
import { HostingDeploy } from "./hosting/context";
20-
import { requirePermissions } from "../requirePermissions";
21-
import { TARGET_PERMISSIONS } from "../commands/deploy";
2220

2321
const TARGETS = {
2422
hosting: HostingTarget,
@@ -62,18 +60,8 @@ export const deploy = async function (
6260
if (targetNames.includes("hosting")) {
6361
const config = options.config.get("hosting");
6462
if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
65-
experiments.assertEnabled("webframeworks", "deploy a web framework to hosting");
66-
const usedToTargetFunctions = targetNames.includes("functions");
63+
experiments.assertEnabled("webframeworks", "deploy a web framework from source");
6764
await prepareFrameworks(targetNames, context, options);
68-
const nowTargetsFunctions = targetNames.includes("functions");
69-
if (nowTargetsFunctions && !usedToTargetFunctions) {
70-
if (context.hostingChannel && !experiments.isEnabled("pintags")) {
71-
throw new FirebaseError(
72-
"Web frameworks with dynamic content do not yet support deploying to preview channels"
73-
);
74-
}
75-
await requirePermissions(TARGET_PERMISSIONS["functions"]);
76-
}
7765
}
7866
}
7967

src/emulator/downloadableEmulators.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet
3333
expectedChecksum: "311609538bd65666eb724ef47c2e6466",
3434
},
3535
firestore: {
36-
version: "1.17.1",
37-
expectedSize: 64778399,
38-
expectedChecksum: "108789dc93092c45b9e04a074e3238ce",
36+
version: "1.17.4",
37+
expectedSize: 64969580,
38+
expectedChecksum: "9d580b58e55e57b0cdc3ca8888098d43",
3939
},
4040
storage: {
4141
version: "1.1.3",

src/experiments.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { bold } from "colorette";
1+
import { bold, italic } from "colorette";
22
import * as leven from "leven";
3+
import { basename } from "path";
34

45
import { configstore } from "./configstore";
56
import { FirebaseError } from "./error";
7+
import { isRunningInGithubAction } from "./init/features/hosting/github";
68

79
export interface Experiment {
810
shortDescription: string;
@@ -192,11 +194,28 @@ export function enableExperimentsFromCliEnvVariable(): void {
192194
*/
193195
export function assertEnabled(name: ExperimentName, task: string): void {
194196
if (!isEnabled(name)) {
195-
throw new FirebaseError(
196-
`Cannot ${task} because the experiment ${bold(name)} is not enabled. To enable ${bold(
197-
name
198-
)} run ${bold(`firebase experiments:enable ${name}`)}`
199-
);
197+
const prefix = `Cannot ${task} because the experiment ${bold(name)} is not enabled.`;
198+
if (isRunningInGithubAction()) {
199+
const path = process.env.GITHUB_WORKFLOW_REF?.split("@")[0];
200+
const filename = path ? `.github/workflows/${basename(path)}` : "your action's yml";
201+
const newValue = [process.env.FIREBASE_CLI_EXPERIMENTS, name].filter((it) => !!it).join(",");
202+
throw new FirebaseError(
203+
`${prefix} To enable add a ${bold(
204+
"FIREBASE_CLI_EXPERIMENTS"
205+
)} environment variable to ${filename}, like so: ${italic(`
206+
207+
- uses: FirebaseExtended/action-hosting-deploy@v0
208+
with:
209+
...
210+
env:
211+
FIREBASE_CLI_EXPERIMENTS: ${newValue}
212+
`)}`
213+
);
214+
} else {
215+
throw new FirebaseError(
216+
`${prefix} To enable ${bold(name)} run ${bold(`firebase experiments:enable ${name}`)}`
217+
);
218+
}
200219
}
201220
}
202221

0 commit comments

Comments
 (0)