How to get the project ID in a Java Cloud Function
As I was working with my colleague Sara Ford on testing the Cloud Functions runtimes for the upcoming “second generation” of the product, rebased on the Cloud Run platform, I wrote a few simple functions for the Java runtime. In one of those Java functions, I wanted to use Google Cloud Storage, to download a file from a bucket. I took a look at the existing sample to download an object:
Storage storage = StorageOptions.newBuilder()
.setProjectId(projectId)
.build()
.getService();
Blob blob = storage.get(BlobId.of(bucketName, objectName));
blob.downloadTo(Paths.get(destFilePath));
I know the name of the bucket, the name of the file, I’m going to store the file in the local file system. So I have all the information needed… except the project ID within which I deployed my Java cloud function. So how do I get the project ID, in Java, inside the Cloud Functions environment?
A previous iteration of Cloud Functions had various useful environment variables available, which included the project ID.
So you could retrieve the ID with a System.getenv()
call.
However, for various compatibility reasons between the various runtimes,
with the Knative open source project, that variable disappeared along the road.
However, I know that the project ID is also part of the internal compute metadata that is accessible via a special URL:
http://metadata.google.internal/computeMetadata/v1/project/project-id
With that knowledge in mind, I thought I could simply make a quick HTTP request to get that information:
private String getProjectId() {
String projectId = null;
HttpURLConnection conn = null;
try {
URL url = new URL("http://metadata.google.internal/computeMetadata/v1/project/project-id");
conn = (HttpURLConnection)(url.openConnection());
conn.setRequestProperty("Metadata-Flavor", "Google");
projectId = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
conn.disconnect();
} catch (Throwable t) {}
return projectId;
}
For the call to work, it is mandatory to set the Metadata-Flavor
header that you see above.
I used Java’s built-in HttpURLConnection
for the job.
There are other HTTP libraries that could’ve made the code simpler, but at first,
I didn’t want to bring another HTTP client, just for retrieving a simple project meta-information.
I’m one of the developers who designed the Functions Framework for Java that is used to craft cloud functions in Java, however, I’ve written quite a few functions using Node.js as well. And in the Node ecosystem, there’s actually an NPM module whose responsibility is to retrieve such project metadata. With the gcp-metadata module, you can require it and then fetch the project ID with:
const gcpMetadata = require('gcp-metadata');
const projectId = await gcpMetadata.project('project-id');
I was surprised I couldn’t easily find an equivalent library in Java. It took me a while to find it, but it actually exists too! That’s the com.google.cloud:google-cloud-core library! And it’s trivial to use:
import com.google.cloud.ServiceOptions;
String projectId = ServiceOptions.getDefaultProjectId();
An extra dependency in my pom.xml, one import and one static method call on ServiceOptions
,
and I can get the GCP project ID! So I’m now able to pass the project ID to my StorageOptions
builder.
But for some reason, I recalled that at times, in some other projects I had written,
I remembered not really needing that project ID information, as the libraries
I was using were smart enough to infer such information from the environment.
Let’s look again at the StorageOptions
from the beginning.
What if I simply omit the setProjectId()
method call?
Lo and behold… indeed, it was actually not required, and the project ID was inferred, transparently.
So I didn’t really need to search for how to retrieve this project ID at all!
And actually, you can further simplify the creation of the StorageOptions down to:
Storage storage = StorageOptions
.getDefaultInstance()
.getService();
At least, now, I know how to retrieve the project ID in Java, in case the libraries or the environment are not providing such details on their own!