Skip to content

Commit 30b0488

Browse files
authored
Auto init (firebase#119)
Read the default config options from FIREBASE_CONFIG env var, allow initializeApp() with no options argument.
1 parent 5124616 commit 30b0488

File tree

9 files changed

+217
-10
lines changed

9 files changed

+217
-10
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@
340340
<goal>verify</goal>
341341
</goals>
342342
</execution>
343-
</executions>
343+
</executions>
344344
</plugin>
345345
<plugin>
346346
<artifactId>maven-javadoc-plugin</artifactId>

src/main/java/com/google/firebase/FirebaseApp.java

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import static com.google.common.base.Preconditions.checkState;
2222
import static java.nio.charset.StandardCharsets.UTF_8;
2323

24+
import com.google.api.client.googleapis.util.Utils;
25+
import com.google.api.client.json.JsonFactory;
26+
import com.google.api.client.json.JsonParser;
2427
import com.google.auth.oauth2.AccessToken;
2528
import com.google.auth.oauth2.GoogleCredentials;
2629
import com.google.auth.oauth2.OAuth2Credentials;
@@ -41,6 +44,9 @@
4144
import com.google.firebase.internal.RevivingScheduledExecutor;
4245
import com.google.firebase.tasks.Task;
4346
import com.google.firebase.tasks.Tasks;
47+
48+
import java.io.FileNotFoundException;
49+
import java.io.FileReader;
4450
import java.io.IOException;
4551
import java.util.ArrayList;
4652
import java.util.Collections;
@@ -79,6 +85,7 @@ public class FirebaseApp {
7985
private static final Map<String, FirebaseApp> instances = new HashMap<>();
8086

8187
public static final String DEFAULT_APP_NAME = "[DEFAULT]";
88+
static final String FIREBASE_CONFIG_ENV_VAR = "FIREBASE_CONFIG";
8289

8390
private static final TokenRefresher.Factory DEFAULT_TOKEN_REFRESHER_FACTORY =
8491
new TokenRefresher.Factory();
@@ -163,13 +170,35 @@ public static FirebaseApp getInstance(@NonNull String name) {
163170
}
164171
}
165172

173+
/**
174+
* Initializes the default {@link FirebaseApp} instance using default credentials and
175+
* default {@link FirebaseOptions} and the {@link #DEFAULT_APP_NAME}.
176+
* Uses Application Default Credentials and also attempts to load {@link FirebaseOptions}
177+
* from the environment. This is done by looking up the {@code FIREBASE_CONFIG} environment
178+
* variable. If the value of the variable starts with <code>'{'</code>, it is parsed as a JSON
179+
* object. Otherwise it is treated as a file name and the JSON content is read from the
180+
* corresponding file.
181+
*/
182+
public static FirebaseApp initializeApp() {
183+
return initializeApp(DEFAULT_APP_NAME);
184+
}
185+
186+
/**
187+
* Initializes a {@link FirebaseApp} instance using default credentials and default
188+
* {@link FirebaseOptions} with a specified name.
189+
* Uses Application Default Credentials and also attempts to load {@link FirebaseOptions}
190+
* from the environment. This is done by looking up the {@code FIREBASE_CONFIG} environment
191+
* variable. If the value of the variable starts with <code>'{'</code>, it is parsed as a JSON
192+
* object. Otherwise it is treated as a file name and the JSON content is read from the
193+
* corresponding file.
194+
*/
195+
public static FirebaseApp initializeApp(String name) {
196+
return initializeApp(getOptionsFromEnvironment(), name);
197+
}
198+
166199
/**
167200
* Initializes the default {@link FirebaseApp} instance. Same as {@link
168-
* #initializeApp(FirebaseOptions, String)}, but it uses {@link #DEFAULT_APP_NAME} as name. *
169-
*
170-
* <p>The creation of the default instance is automatically triggered at app startup time, if
171-
* Firebase configuration values are available from resources - populated from
172-
* google-services.json.
201+
* #initializeApp(FirebaseOptions, String)}, but uses {@link #DEFAULT_APP_NAME} as name.
173202
*/
174203
public static FirebaseApp initializeApp(FirebaseOptions options) {
175204
return initializeApp(options, DEFAULT_APP_NAME);
@@ -542,4 +571,34 @@ enum State {
542571
STOPPED
543572
}
544573
}
574+
575+
private static FirebaseOptions getOptionsFromEnvironment() {
576+
String defaultConfig = System.getenv(FIREBASE_CONFIG_ENV_VAR);
577+
try {
578+
if (Strings.isNullOrEmpty(defaultConfig)) {
579+
return new FirebaseOptions.Builder()
580+
.setCredentials(GoogleCredentials.getApplicationDefault())
581+
.build();
582+
}
583+
JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
584+
FirebaseOptions.Builder builder = new FirebaseOptions.Builder();
585+
JsonParser parser;
586+
if (defaultConfig.startsWith("{")) {
587+
parser = jsonFactory.createJsonParser(defaultConfig);
588+
} else {
589+
FileReader reader;
590+
reader = new FileReader(defaultConfig);
591+
parser = jsonFactory.createJsonParser(reader);
592+
}
593+
parser.parseAndClose(builder);
594+
builder.setCredentials(GoogleCredentials.getApplicationDefault());
595+
596+
return builder.build();
597+
598+
} catch (FileNotFoundException e) {
599+
throw new IllegalStateException(e);
600+
} catch (IOException e) {
601+
throw new IllegalStateException(e);
602+
}
603+
}
545604
}

src/main/java/com/google/firebase/FirebaseOptions.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.api.client.googleapis.util.Utils;
2323
import com.google.api.client.http.HttpTransport;
2424
import com.google.api.client.json.JsonFactory;
25+
import com.google.api.client.util.Key;
2526
import com.google.auth.oauth2.GoogleCredentials;
2627
import com.google.common.base.Strings;
2728
import com.google.firebase.auth.FirebaseCredential;
@@ -136,12 +137,19 @@ ThreadManager getThreadManager() {
136137
* Builder for constructing {@link FirebaseOptions}.
137138
*/
138139
public static final class Builder {
139-
140+
@Key("databaseAuthVariableOverride")
141+
private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
142+
143+
@Key("databaseUrl")
140144
private String databaseUrl;
145+
146+
@Key("projectId")
147+
private String projectId;
148+
149+
@Key("storageBucket")
141150
private String storageBucket;
151+
142152
private GoogleCredentials credentials;
143-
private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
144-
private String projectId;
145153
private HttpTransport httpTransport = Utils.getDefaultTransport();
146154
private JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
147155
private ThreadManager threadManager = FirebaseThreadManagers.DEFAULT_THREAD_MANAGER;
@@ -150,7 +158,7 @@ public static final class Builder {
150158
public Builder() {}
151159

152160
/**
153-
* Initializes the builder's values from the options object. *
161+
* Initializes the builder's values from the options object.
154162
*
155163
* <p>The new builder is not backed by this objects values, that is changes made to the new
156164
* builder don't change the values of the origin object.

src/test/java/com/google/firebase/FirebaseAppTest.java

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@
3434
import com.google.auth.oauth2.OAuth2Credentials;
3535
import com.google.auth.oauth2.OAuth2Credentials.CredentialsChangedListener;
3636
import com.google.common.base.Defaults;
37+
import com.google.common.collect.ImmutableMap;
3738
import com.google.common.io.BaseEncoding;
3839
import com.google.firebase.FirebaseApp.TokenRefresher;
3940
import com.google.firebase.FirebaseOptions.Builder;
4041
import com.google.firebase.database.FirebaseDatabase;
4142
import com.google.firebase.testing.FirebaseAppRule;
4243
import com.google.firebase.testing.ServiceAccount;
4344
import com.google.firebase.testing.TestUtils;
45+
import java.io.File;
4446
import java.io.IOException;
4547
import java.lang.reflect.InvocationTargetException;
4648
import java.lang.reflect.Method;
@@ -50,6 +52,7 @@
5052
import java.util.Collection;
5153
import java.util.Date;
5254
import java.util.List;
55+
import java.util.Map;
5356
import java.util.UUID;
5457
import java.util.concurrent.Callable;
5558
import java.util.concurrent.ExecutionException;
@@ -419,6 +422,114 @@ public void testTokenRefresherStateMachine() {
419422
assertEquals(0, refresher.cancelCalls);
420423
}
421424

425+
@Test(expected = IllegalArgumentException.class)
426+
public void testEmptyFirebaseConfigFile() {
427+
setFirebaseConfigEnvironmentVariable("firebase_config_empty.json");
428+
FirebaseApp.initializeApp();
429+
}
430+
431+
@Test
432+
public void testEmptyFirebaseConfigString() {
433+
setFirebaseConfigEnvironmentVariable("");
434+
FirebaseApp firebaseApp = FirebaseApp.initializeApp();
435+
assertEquals(null, firebaseApp.getOptions().getProjectId());
436+
assertEquals(null, firebaseApp.getOptions().getStorageBucket());
437+
assertEquals(null, firebaseApp.getOptions().getDatabaseUrl());
438+
assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty());
439+
}
440+
441+
@Test
442+
public void testEmptyFirebaseConfigJSONObject() {
443+
setFirebaseConfigEnvironmentVariable("{}");
444+
FirebaseApp firebaseApp = FirebaseApp.initializeApp();
445+
assertEquals(null, firebaseApp.getOptions().getProjectId());
446+
assertEquals(null, firebaseApp.getOptions().getStorageBucket());
447+
assertEquals(null, firebaseApp.getOptions().getDatabaseUrl());
448+
assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty());
449+
}
450+
451+
@Test(expected = IllegalStateException.class)
452+
public void testInvalidFirebaseConfigFile() {
453+
setFirebaseConfigEnvironmentVariable("firebase_config_invalid.json");
454+
FirebaseApp.initializeApp();
455+
}
456+
457+
@Test(expected = IllegalStateException.class)
458+
public void testInvalidFirebaseConfigString() {
459+
setFirebaseConfigEnvironmentVariable("{,,");
460+
FirebaseApp.initializeApp();
461+
}
462+
463+
@Test(expected = IllegalStateException.class)
464+
public void testFirebaseConfigMissingFile() {
465+
setFirebaseConfigEnvironmentVariable("no_such.json");
466+
FirebaseApp.initializeApp();
467+
}
468+
469+
@Test
470+
public void testFirebaseConfigFileWithSomeKeysMissing() {
471+
setFirebaseConfigEnvironmentVariable("firebase_config_partial.json");
472+
FirebaseApp firebaseApp = FirebaseApp.initializeApp();
473+
assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
474+
assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl());
475+
}
476+
477+
@Test
478+
public void testValidFirebaseConfigFile() {
479+
setFirebaseConfigEnvironmentVariable("firebase_config.json");
480+
FirebaseApp firebaseApp = FirebaseApp.initializeApp();
481+
assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
482+
assertEquals("hipster-chat.appspot.mock", firebaseApp.getOptions().getStorageBucket());
483+
assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl());
484+
assertEquals("testuser", firebaseApp.getOptions().getDatabaseAuthVariableOverride().get("uid"));
485+
}
486+
487+
@Test
488+
public void testEnvironmentVariableIgnored() {
489+
setFirebaseConfigEnvironmentVariable("firebase_config.json");
490+
FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS);
491+
assertEquals(null, firebaseApp.getOptions().getProjectId());
492+
assertEquals(null, firebaseApp.getOptions().getStorageBucket());
493+
assertEquals(null, firebaseApp.getOptions().getDatabaseUrl());
494+
assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty());
495+
}
496+
497+
@Test
498+
public void testValidFirebaseConfigString() {
499+
setFirebaseConfigEnvironmentVariable("{"
500+
+ "\"databaseAuthVariableOverride\": {"
501+
+ "\"uid\":"
502+
+ "\"testuser\""
503+
+ "},"
504+
+ "\"databaseUrl\": \"https://hipster-chat.firebaseio.mock\","
505+
+ "\"projectId\": \"hipster-chat-mock\","
506+
+ "\"storageBucket\": \"hipster-chat.appspot.mock\""
507+
+ "}");
508+
FirebaseApp firebaseApp = FirebaseApp.initializeApp();
509+
assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
510+
assertEquals("hipster-chat.appspot.mock", firebaseApp.getOptions().getStorageBucket());
511+
assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl());
512+
assertEquals("testuser",
513+
firebaseApp.getOptions().getDatabaseAuthVariableOverride().get("uid"));
514+
}
515+
516+
@Test
517+
public void testFirebaseConfigFileIgnoresInvalidKey() {
518+
setFirebaseConfigEnvironmentVariable("firebase_config_invalid_key.json");
519+
FirebaseApp firebaseApp = FirebaseApp.initializeApp();
520+
assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
521+
}
522+
523+
@Test
524+
public void testFirebaseConfigStringIgnoresInvalidKey() {
525+
setFirebaseConfigEnvironmentVariable("{"
526+
+ "\"databaseUareL\": \"https://hipster-chat.firebaseio.mock\","
527+
+ "\"projectId\": \"hipster-chat-mock\""
528+
+ "}");
529+
FirebaseApp firebaseApp = FirebaseApp.initializeApp();
530+
assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
531+
}
532+
422533
@Test(expected = IllegalArgumentException.class)
423534
public void testFirebaseExceptionNullDetail() {
424535
new FirebaseException(null);
@@ -428,6 +539,20 @@ public void testFirebaseExceptionNullDetail() {
428539
public void testFirebaseExceptionEmptyDetail() {
429540
new FirebaseException("");
430541
}
542+
543+
544+
private void setFirebaseConfigEnvironmentVariable(String configJSON) {
545+
String configValue = "";
546+
if (configJSON.isEmpty() || configJSON.startsWith("{")) {
547+
configValue = configJSON;
548+
} else {
549+
configValue = new File("src/test/resources/" + configJSON)
550+
.getAbsolutePath();
551+
}
552+
Map<String, String> environmentVariables =
553+
ImmutableMap.of(FirebaseApp.FIREBASE_CONFIG_ENV_VAR , configValue);
554+
TestUtils.setEnvironmentVariables(environmentVariables);
555+
}
431556

432557
private static class MockTokenRefresher extends TokenRefresher {
433558

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"databaseAuthVariableOverride": {"uid": "testuser"},
3+
"databaseUrl": "https://hipster-chat.firebaseio.mock",
4+
"projectId": "hipster-chat-mock",
5+
"storageBucket": "hipster-chat.appspot.mock"
6+
}

src/test/resources/firebase_config_empty.json

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
baaaaad
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"databaseUareL": "https://hipster-chat.firebaseio.mock",
3+
"projectId": "hipster-chat-mock"
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"databaseUrl": "https://hipster-chat.firebaseio.mock",
3+
"projectId": "hipster-chat-mock"
4+
}

0 commit comments

Comments
 (0)