GSoC Journal: Week 5 — Giving the Pipeline the Keys
There’s a point in release automation where getting the pipeline to work stops being enough. Once your CI can build an Android App Bundle, sign it, and send it towards production, you have to start asking a slightly more uncomfortable question: who actually has the signing key?
That question ended up defining most of Week 5.
Giving the Pipeline the Keys
The signing key is probably the most sensitive part of the entire release process, and I didn’t want the CI environment to ever need direct access to the private key. So I started moving the signing architecture over to Google Cloud KMS.
The idea is fairly simple: instead of giving GitHub Actions a keystore and letting it sign the bundle itself, the pipeline can ask Cloud KMS to perform the signing operation. The private key stays inside Google’s managed key infrastructure; the workflow gets the signature back.
Actually implementing that was considerably less simple.
I introduced a CloudSigner abstraction with a production CloudKmsSigner and a FakeCloudSigner for testing, along with a dedicated signing step for the release binary. This kept the signing mechanism separate from the rest of the deployment logic and, importantly, meant that tests didn't need to make real KMS requests.
And Then There Was Bazel
Of course, Cloud KMS signing needed another piece: jsign-core.
Adding a Maven dependency in a Bazel project is not quite as straightforward as adding a line to a Gradle file. Oppia’s pinned Maven setup meant I had to deal with the dependency lockfile as well, eventually putting together a small one-shot workflow to repin the Maven dependencies and regenerate the required files.
At one point I had written CI automation whose job was to update the dependencies needed by the CI automation. I’m choosing to call that architecture.
Making Production Say “Not Yet”
The Play Console work from the previous week also continued to grow.
Get Sandesh Raj’s stories in your inbox
Join Medium for free to get updates from this writer.
The deployment logic was becoming much more deliberate about what it was allowed to do. Version inversion checks, pending-release checks, changelog validation, rollout fractions, and the actual Play Console Edit lifecycle all had to fit together without letting an automated workflow blindly cause side effects.
The more I worked on it, the more I liked one simple principle:
Check first. Cause side effects second.
That also influenced the testing architecture. The Play Console client was kept behind an interface, with fake implementations for the release logic and HTTP-level testing around the actual API client. I wanted to be able to exercise the complicated parts of the deployment process extensively without having every test interact with the real Play Console.
Starting to Feel Like a Release System
By the end of Week 5, the project was starting to feel less like a collection of GitHub Actions workflows and more like an actual release system.
Build the bundle. Sign it without exposing the private key. Validate the release. Decide which track and rollout it belongs to. Update the changelog. Talk to Play Console. And, most importantly, make sure the automation knows when it shouldn’t proceed.
The happy path was never particularly difficult. The interesting engineering was making all the things around it safe enough that I could eventually trust the pipeline with a real production release.
Week 5 done.
The pipeline has the keys.
It just doesn’t get to keep them.
Stay tuned for more updates on my journey with Oppia Foundation in GSoC’26…
Find me on social media:
- LinkedIn: https://www.linkedin.com/in/sandesh282/
- GitHub: https://github.com/sandesh282/







