diff --git a/src/init/features/storage.spec.ts b/src/init/features/storage.spec.ts index 348b92f4caa..1e5d8e53fc7 100644 --- a/src/init/features/storage.spec.ts +++ b/src/init/features/storage.spec.ts @@ -5,7 +5,7 @@ import * as sinon from "sinon"; import { Config } from "../../config"; import { askQuestions, actuate } from "./storage"; import * as prompt from "../../prompt"; - +import * as gcp from "../../gcp"; describe("storage", () => { const sandbox: sinon.SinonSandbox = sinon.createSandbox(); let askWriteProjectFileStub: sinon.SinonStub; @@ -39,4 +39,29 @@ describe("storage", () => { expect(_.get(setup, "config.storage.rules")).to.deep.equal("storage.rules"); }); }); + it("should download rules from console", async () => { + const setup = { + config: {}, + rcfile: { projects: {}, targets: {}, etags: {} }, + projectId: "test-project", + instructions: [], + }; + const config = new Config({}, { projectDir: "test", cwd: "test" }); + const getRulesetNameStub = sandbox + .stub(gcp.rules, "getLatestRulesetName") + .resolves("ruleset-name"); + const getRulesetContentStub = sandbox + .stub(gcp.rules, "getRulesetContent") + .resolves([{ name: "file.rules", content: "console rules" }]); + const writeStub = sandbox.stub(config, "confirmWriteProjectFile").resolves(true); + promptStub.returns("storage.rules"); + + await askQuestions(setup, config); + await actuate(setup, config); + + expect(getRulesetNameStub.calledOnceWith("test-project", "firebase.storage")).to.be.true; + expect(getRulesetContentStub.calledOnceWith("ruleset-name")).to.be.true; + expect(writeStub.calledOnceWith("storage.rules", "console rules")).to.be.true; + expect(_.get(setup, "featureInfo.storage.rules")).to.equal("console rules"); + }); }); diff --git a/src/init/features/storage.ts b/src/init/features/storage.ts index 56663287ec2..352107db3fa 100644 --- a/src/init/features/storage.ts +++ b/src/init/features/storage.ts @@ -6,7 +6,8 @@ import { readTemplateSync } from "../../templates"; import { Config } from "../../config"; import { Setup } from ".."; import { FirebaseError } from "../../error"; - +import * as gcp from "../../gcp"; +import * as utils from "../../utils"; export interface RequiredInfo { rulesFilename: string; rules: string; @@ -15,7 +16,6 @@ export interface RequiredInfo { const RULES_TEMPLATE = readTemplateSync("init/storage/storage.rules"); const DEFAULT_RULES_FILE = "storage.rules"; - export async function askQuestions(setup: Setup, config: Config): Promise { logger.info(); logger.info("Firebase Storage Security Rules allow you to define how and when to allow"); @@ -32,6 +32,14 @@ export async function askQuestions(setup: Setup, config: Config): Promise message: "What file should be used for Storage Rules?", default: DEFAULT_RULES_FILE, }); + if (setup.projectId) { + const downloadedRules = await getRulesFromConsole(setup.projectId); + if (downloadedRules) { + info.rules = downloadedRules; + utils.logBullet(`Downloaded the existing Storage Security Rules from the Firebase console`); + } + } + info.writeRules = await config.confirmWriteProjectFile(info.rulesFilename, info.rules); // Populate featureInfo for the actuate step later. setup.featureInfo = setup.featureInfo || {}; @@ -54,3 +62,18 @@ export async function actuate(setup: Setup, config: Config): Promise { config.writeProjectFile(info.rulesFilename, info.rules); } } +async function getRulesFromConsole(projectId: string): Promise { + const name = await gcp.rules.getLatestRulesetName(projectId, "firebase.storage"); + if (!name) { + return null; + } + const rules = await gcp.rules.getRulesetContent(name); + if (rules.length === 0) { + return utils.reject("Ruleset has no files", { exit: 1 }); + } + + if (rules.length > 1) { + return utils.reject(`Ruleset has too many files: ${rules.length}`, { exit: 1 }); + } + return rules[0].content; +}