Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- Added 'hosting' to the 'firebase_init' MCP tool.
- Added 'hosting' to the 'firebase_init' MCP tool (#9375)
- Revert logic to abort function deploys in non-interactive mode when secrets are missing. (#9378)
28 changes: 28 additions & 0 deletions src/deploy/functions/params.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import * as prompt from "../../prompt";
import * as params from "./params";
import * as secretManager from "../../gcp/secretManager";
import { FirebaseError } from "../../error";

const expect = chai.expect;
const fakeConfig = {
Expand Down Expand Up @@ -214,7 +216,7 @@
];
input.resolves("baz");
await params.resolveParams(paramsToResolve, fakeConfig, {});
expect(input.getCall(1).args[0].default).to.eq("baz");

Check warning on line 219 in src/deploy/functions/params.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .default on an `any` value
});

it("can resolve a CEL expression containing only identities", async () => {
Expand All @@ -234,7 +236,7 @@
];
input.resolves("baz");
await params.resolveParams(paramsToResolve, fakeConfig, {});
expect(input.getCall(1).args[0].default).to.eq("baz/quox");

Check warning on line 239 in src/deploy/functions/params.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .default on an `any` value
});

it("can resolve a CEL expression depending on the internal params", async () => {
Expand All @@ -260,9 +262,9 @@
];
input.resolves("baz");
await params.resolveParams(paramsToResolve, fakeConfig, {});
expect(input.getCall(0).args[0].default).to.eq("https://foo.firebaseio.com/quox");

Check warning on line 265 in src/deploy/functions/params.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .default on an `any` value
expect(input.getCall(1).args[0].default).to.eq("projectID: foo");

Check warning on line 266 in src/deploy/functions/params.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .default on an `any` value
expect(input.getCall(2).args[0].default).to.eq(

Check warning on line 267 in src/deploy/functions/params.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .default on an `any` value
"http://foo.appspot.com.storage.googleapis.com/",
);
});
Expand Down Expand Up @@ -298,4 +300,30 @@
input.resolves("22");
await expect(params.resolveParams(paramsToResolve, fakeConfig, {})).to.eventually.be.rejected;
});

it("does not throw in non-interactive mode if secret exists in cloud", async () => {
const paramsToResolve: params.Param[] = [{ name: "MY_SECRET", type: "secret" }];
const getSecretMetadataStub = sinon.stub(secretManager, "getSecretMetadata").resolves({
secret: { name: "MY_SECRET", projectId: "foo", labels: {}, replication: {} },
secretVersion: { versionId: "1", state: "ENABLED", secret: {} as any },

Check warning on line 308 in src/deploy/functions/params.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type

Check warning on line 308 in src/deploy/functions/params.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
});

await expect(params.resolveParams(paramsToResolve, fakeConfig, {}, true)).to.be.fulfilled;

getSecretMetadataStub.restore();
});

it("throws in non-interactive mode if secret is missing in cloud", async () => {
const paramsToResolve: params.Param[] = [{ name: "MY_SECRET", type: "secret" }];
const getSecretMetadataStub = sinon.stub(secretManager, "getSecretMetadata").resolves({
secret: undefined,
});

await expect(params.resolveParams(paramsToResolve, fakeConfig, {}, true)).to.be.rejectedWith(
FirebaseError,
/In non-interactive mode but have no value for the secret/,
);

getSecretMetadataStub.restore();
});
});
31 changes: 13 additions & 18 deletions src/deploy/functions/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@
return pv;
}

setDelimiter(delimiter: string) {

Check warning on line 275 in src/deploy/functions/params.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function
this.delimiter = delimiter;
}

Expand All @@ -299,7 +299,7 @@
if (this.rawValue.includes("[")) {
// Convert quotes to apostrophes
const unquoted = this.rawValue.replace(/'/g, '"');
return JSON.parse(unquoted);

Check warning on line 302 in src/deploy/functions/params.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe return of an `any` typed value
}

// Continue to handle something like "a,b,c"
Expand Down Expand Up @@ -395,26 +395,10 @@

const [needSecret, needPrompt] = partition(outstanding, (param) => param.type === "secret");

// Check for missing secrets in non-interactive mode
if (nonInteractive && needSecret.length > 0) {
const secretNames = needSecret.map((p) => p.name).join(", ");
const commands = needSecret
.map(
(p) =>
`\tfirebase functions:secrets:set ${p.name}${(p as SecretParam).format === "json" ? " --format=json --data-file <file.json>" : ""}`,
)
.join("\n");
throw new FirebaseError(
`In non-interactive mode but have no value for the following secrets: ${secretNames}\n\n` +
"Set these secrets before deploying:\n" +
commands,
);
}

// The functions emulator will handle secrets
if (!isEmulator) {
for (const param of needSecret) {
await handleSecret(param as SecretParam, firebaseConfig.projectId);
await handleSecret(param as SecretParam, firebaseConfig.projectId, nonInteractive);
}
}

Expand All @@ -434,7 +418,7 @@
}
if (paramDefault && !canSatisfyParam(param, paramDefault)) {
throw new FirebaseError(
"Parameter " + param.name + " has default value " + paramDefault + " of wrong type",

Check warning on line 421 in src/deploy/functions/params.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Operands of '+' operation must either be both strings or both numbers. Consider using a template literal
);
}
paramValues[param.name] = await promptParam(param, firebaseConfig.projectId, paramDefault);
Expand Down Expand Up @@ -481,9 +465,20 @@
* to read its environment variables. They are instead provided through GCF's own
* Secret Manager integration.
*/
async function handleSecret(secretParam: SecretParam, projectId: string): Promise<void> {
async function handleSecret(
secretParam: SecretParam,
projectId: string,
nonInteractive?: boolean,
): Promise<void> {
const metadata = await secretManager.getSecretMetadata(projectId, secretParam.name, "latest");
if (!metadata.secret) {
if (nonInteractive) {
throw new FirebaseError(
`In non-interactive mode but have no value for the secret: ${secretParam.name}\n\n` +
"Set this secret before deploying:\n" +
`\tfirebase functions:secrets:set ${secretParam.name}${secretParam.format === "json" ? " --format=json --data-file <file.json>" : ""}`,
);
}
const promptMessage = `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${
secretParam.name
}. Enter ${secretParam.format === "json" ? "a JSON value" : "a value"} for ${
Expand Down
Loading