Skip to content

Commit 5ffcd45

Browse files
Add support for Firebase token as authentication
1 parent 84f57e0 commit 5ffcd45

File tree

6 files changed

+83
-39
lines changed

6 files changed

+83
-39
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ A full setup guide can be found [in the Firebase Hosting docs](https://firebase.
1212
The [Firebase CLI](https://firebase.google.com/docs/cli) can get you set up quickly with a default configuration.
1313

1414
- If you've NOT set up Hosting, run this version of the command from the root of your local directory:
15+
1516
```bash
1617
firebase init hosting
1718
```
1819

1920
- If you've ALREADY set up Hosting, then you just need to set up the GitHub Action part of Hosting.
2021
Run this version of the command from the root of your local directory:
22+
2123
```bash
2224
firebase init hosting:github
2325
```

action.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ inputs:
2626
description: "The GITHUB_TOKEN secret"
2727
required: false
2828
firebaseServiceAccount:
29-
description: "Firebase service account JSON"
30-
required: true
29+
description: "Firebase service account JSON. Either this or firebaseToken should be defined"
30+
required: false
31+
firebaseToken:
32+
description: "Firebase token generated by login:ci. Either this or firebaseServiceAccount should be defined"
33+
required: false
3134
expires:
3235
description: "How long should a preview live? See the preview channels docs for options"
3336
default: "7d"

bin/action.min.js

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11189,8 +11189,13 @@ function interpretChannelDeployResult(deployResult) {
1118911189
};
1119011190
}
1119111191

11192-
async function execWithCredentials(args, projectId, gacFilename, debug = false) {
11192+
async function execWithCredentials(args, projectId, credentials, debug = false) {
1119311193
let deployOutputBuf = [];
11194+
const commandCredentials = "gacFilename" in credentials ? {
11195+
GOOGLE_APPLICATION_CREDENTIALS: credentials.gacFilename
11196+
} : {
11197+
FIREBASE_TOKEN: credentials.firebaseToken
11198+
};
1119411199

1119511200
try {
1119611201
await exec_2("npx firebase-tools", [...args, ...(projectId ? ["--project", projectId] : []), debug ? "--debug" // gives a more thorough error message
@@ -11202,17 +11207,16 @@ async function execWithCredentials(args, projectId, gacFilename, debug = false)
1120211207

1120311208
},
1120411209
env: _extends({}, process.env, {
11205-
FIREBASE_DEPLOY_AGENT: "action-hosting-deploy",
11206-
GOOGLE_APPLICATION_CREDENTIALS: gacFilename
11207-
})
11210+
FIREBASE_DEPLOY_AGENT: "action-hosting-deploy"
11211+
}, commandCredentials)
1120811212
});
1120911213
} catch (e) {
1121011214
console.log(Buffer.concat(deployOutputBuf).toString("utf-8"));
1121111215
console.log(e.message);
1121211216

1121311217
if (debug === false) {
1121411218
console.log("Retrying deploy with the --debug flag for better error output");
11215-
await execWithCredentials(args, projectId, gacFilename, true);
11219+
await execWithCredentials(args, projectId, credentials, true);
1121611220
} else {
1121711221
throw e;
1121811222
}
@@ -11221,23 +11225,23 @@ async function execWithCredentials(args, projectId, gacFilename, debug = false)
1122111225
return deployOutputBuf.length ? deployOutputBuf[deployOutputBuf.length - 1].toString("utf-8") : ""; // output from the CLI
1122211226
}
1122311227

11224-
async function deployPreview(gacFilename, deployConfig) {
11228+
async function deployPreview(credentials, deployConfig) {
1122511229
const {
1122611230
projectId,
1122711231
channelId,
1122811232
target,
1122911233
expires
1123011234
} = deployConfig;
11231-
const deploymentText = await execWithCredentials(["hosting:channel:deploy", channelId, ...(target ? ["--only", target] : []), ...(expires ? ["--expires", expires] : [])], projectId, gacFilename);
11235+
const deploymentText = await execWithCredentials(["hosting:channel:deploy", channelId, ...(target ? ["--only", target] : []), ...(expires ? ["--expires", expires] : [])], projectId, credentials);
1123211236
const deploymentResult = JSON.parse(deploymentText.trim());
1123311237
return deploymentResult;
1123411238
}
11235-
async function deployProductionSite(gacFilename, productionDeployConfig) {
11239+
async function deployProductionSite(credentials, productionDeployConfig) {
1123611240
const {
1123711241
projectId,
1123811242
target
1123911243
} = productionDeployConfig;
11240-
const deploymentText = await execWithCredentials(["deploy", "--only", `hosting${target ? ":" + target : ""}`], projectId, gacFilename);
11244+
const deploymentText = await execWithCredentials(["deploy", "--only", `hosting${target ? ":" + target : ""}`], projectId, credentials);
1124111245
const deploymentResult = JSON.parse(deploymentText);
1124211246
return deploymentResult;
1124311247
}
@@ -11404,9 +11408,8 @@ async function postChannelSuccessComment(github, context, result, commit) {
1140411408

1140511409
const expires = core.getInput("expires");
1140611410
const projectId = core.getInput("projectId");
11407-
const googleApplicationCredentials = core.getInput("firebaseServiceAccount", {
11408-
required: true
11409-
});
11411+
const googleApplicationCredentials = core.getInput("firebaseServiceAccount");
11412+
const firebaseToken = core.getInput("firebaseToken");
1141011413
const configuredChannelId = core.getInput("channelId");
1141111414
const isProductionDeploy = configuredChannelId === "live";
1141211415
const token = process.env.GITHUB_TOKEN || core.getInput("repoToken");
@@ -11444,13 +11447,28 @@ async function run() {
1144411447

1144511448
core.endGroup();
1144611449
core.startGroup("Setting up CLI credentials");
11447-
const gacFilename = await createGacFile(googleApplicationCredentials);
11448-
console.log("Created a temporary file with Application Default Credentials.");
11450+
let credentials;
11451+
11452+
if (googleApplicationCredentials) {
11453+
console.log("Using Google Application Credentials...");
11454+
credentials = {
11455+
gacFilename: await createGacFile(googleApplicationCredentials)
11456+
};
11457+
console.log("Created a temporary file with Application Default Credentials.");
11458+
} else if (firebaseToken) {
11459+
console.log("Using firebase token...");
11460+
credentials = {
11461+
firebaseToken
11462+
};
11463+
} else {
11464+
throw new Error("Either googleApplicationCredential or firebaseToken should be defined");
11465+
}
11466+
1144911467
core.endGroup();
1145011468

1145111469
if (isProductionDeploy) {
1145211470
core.startGroup("Deploying to production site");
11453-
const deployment = await deployProductionSite(gacFilename, {
11471+
const deployment = await deployProductionSite(credentials, {
1145411472
projectId,
1145511473
target
1145611474
});
@@ -11475,7 +11493,7 @@ async function run() {
1147511493

1147611494
const channelId = getChannelId(configuredChannelId, github.context);
1147711495
core.startGroup(`Deploying to Firebase preview channel ${channelId}`);
11478-
const deployment = await deployPreview(gacFilename, {
11496+
const deployment = await deployPreview(credentials, {
1147911497
projectId,
1148011498
expires,
1148111499
channelId,

src/deploy.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export type ProductionDeployConfig = {
5252
target?: string;
5353
};
5454

55+
export type Credentials = { gacFilename: string } | { firebaseToken: string };
56+
5557
export function interpretChannelDeployResult(
5658
deployResult: ChannelSuccessResult
5759
): { expireTime: string; urls: string[] } {
@@ -68,12 +70,17 @@ export function interpretChannelDeployResult(
6870

6971
async function execWithCredentials(
7072
args: string[],
71-
projectId,
72-
gacFilename,
73+
projectId: string,
74+
credentials: Credentials,
7375
debug: boolean = false
7476
) {
7577
let deployOutputBuf: Buffer[] = [];
7678

79+
const commandCredentials =
80+
"gacFilename" in credentials
81+
? { GOOGLE_APPLICATION_CREDENTIALS: credentials.gacFilename }
82+
: { FIREBASE_TOKEN: credentials.firebaseToken };
83+
7784
try {
7885
await exec(
7986
"npx firebase-tools",
@@ -93,7 +100,7 @@ async function execWithCredentials(
93100
env: {
94101
...process.env,
95102
FIREBASE_DEPLOY_AGENT: "action-hosting-deploy",
96-
GOOGLE_APPLICATION_CREDENTIALS: gacFilename, // the CLI will automatically authenticate with this env variable set
103+
...commandCredentials,
97104
},
98105
}
99106
);
@@ -105,7 +112,7 @@ async function execWithCredentials(
105112
console.log(
106113
"Retrying deploy with the --debug flag for better error output"
107114
);
108-
await execWithCredentials(args, projectId, gacFilename, true);
115+
await execWithCredentials(args, projectId, credentials, true);
109116
} else {
110117
throw e;
111118
}
@@ -117,7 +124,7 @@ async function execWithCredentials(
117124
}
118125

119126
export async function deployPreview(
120-
gacFilename: string,
127+
credentials: Credentials,
121128
deployConfig: DeployConfig
122129
) {
123130
const { projectId, channelId, target, expires } = deployConfig;
@@ -130,7 +137,7 @@ export async function deployPreview(
130137
...(expires ? ["--expires", expires] : []),
131138
],
132139
projectId,
133-
gacFilename
140+
credentials
134141
);
135142

136143
const deploymentResult = JSON.parse(deploymentText.trim()) as
@@ -141,15 +148,15 @@ export async function deployPreview(
141148
}
142149

143150
export async function deployProductionSite(
144-
gacFilename,
151+
credentials: Credentials,
145152
productionDeployConfig: ProductionDeployConfig
146153
) {
147154
const { projectId, target } = productionDeployConfig;
148155

149156
const deploymentText = await execWithCredentials(
150157
["deploy", "--only", `hosting${target ? ":" + target : ""}`],
151158
projectId,
152-
gacFilename
159+
credentials
153160
);
154161

155162
const deploymentResult = JSON.parse(deploymentText) as

src/index.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { existsSync } from "fs";
2626
import { createCheck } from "./createCheck";
2727
import { createGacFile } from "./createGACFile";
2828
import {
29+
Credentials,
2930
deployPreview,
3031
deployProductionSite,
3132
ErrorResult,
@@ -40,9 +41,8 @@ import {
4041
// Inputs defined in action.yml
4142
const expires = getInput("expires");
4243
const projectId = getInput("projectId");
43-
const googleApplicationCredentials = getInput("firebaseServiceAccount", {
44-
required: true,
45-
});
44+
const googleApplicationCredentials = getInput("firebaseServiceAccount");
45+
const firebaseToken = getInput("firebaseToken");
4646
const configuredChannelId = getInput("channelId");
4747
const isProductionDeploy = configuredChannelId === "live";
4848
const token = process.env.GITHUB_TOKEN || getInput("repoToken");
@@ -80,15 +80,29 @@ async function run() {
8080
endGroup();
8181

8282
startGroup("Setting up CLI credentials");
83-
const gacFilename = await createGacFile(googleApplicationCredentials);
84-
console.log(
85-
"Created a temporary file with Application Default Credentials."
86-
);
83+
let credentials: Credentials;
84+
85+
if (googleApplicationCredentials) {
86+
console.log("Using Google Application Credentials...");
87+
credentials = {
88+
gacFilename: await createGacFile(googleApplicationCredentials),
89+
};
90+
console.log(
91+
"Created a temporary file with Application Default Credentials."
92+
);
93+
} else if (firebaseToken) {
94+
console.log("Using firebase token...");
95+
credentials = { firebaseToken };
96+
} else {
97+
throw new Error(
98+
"Either googleApplicationCredential or firebaseToken should be defined"
99+
);
100+
}
87101
endGroup();
88102

89103
if (isProductionDeploy) {
90104
startGroup("Deploying to production site");
91-
const deployment = await deployProductionSite(gacFilename, {
105+
const deployment = await deployProductionSite(credentials, {
92106
projectId,
93107
target,
94108
});
@@ -113,7 +127,7 @@ async function run() {
113127
const channelId = getChannelId(configuredChannelId, context);
114128

115129
startGroup(`Deploying to Firebase preview channel ${channelId}`);
116-
const deployment = await deployPreview(gacFilename, {
130+
const deployment = await deployPreview(credentials, {
117131
projectId,
118132
expires,
119133
channelId,

test/deploy.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe("deploy", () => {
7070
exec.exec = jest.fn(fakeExec).mockImplementationOnce(fakeExecFail);
7171

7272
const deployOutput: ChannelSuccessResult = (await deployPreview(
73-
"my-file",
73+
{ gacFilename: "my-file" },
7474
baseChannelDeployConfig
7575
)) as ChannelSuccessResult;
7676

@@ -94,7 +94,7 @@ describe("deploy", () => {
9494
exec.exec = jest.fn(fakeExec);
9595

9696
const deployOutput: ChannelSuccessResult = (await deployPreview(
97-
"my-file",
97+
{ gacFilename: "my-file" },
9898
baseChannelDeployConfig
9999
)) as ChannelSuccessResult;
100100

@@ -117,7 +117,7 @@ describe("deploy", () => {
117117
target: "my-second-site",
118118
};
119119

120-
await deployPreview("my-file", config);
120+
await deployPreview({ gacFilename: "my-file" }, config);
121121

122122
// Check the arguments that exec was called with
123123
// @ts-ignore Jest adds a magic "mock" property
@@ -134,7 +134,7 @@ describe("deploy", () => {
134134
exec.exec = jest.fn(fakeExec);
135135

136136
const deployOutput: ProductionSuccessResult = (await deployProductionSite(
137-
"my-file",
137+
{ gacFilename: "my-file" },
138138
baseLiveDeployConfig
139139
)) as ProductionSuccessResult;
140140

0 commit comments

Comments
 (0)