top of page

Project Onboarding

Services used:
Pub/Sub, Cloud Run, Eventarc, IAM, Secret Manager, Cloud Storage, Terraform

Extra tools:
Python, Gmail API, Git, GitHub, GitHub Actions

Rauni Ribeiro

By Rauni Ribeiro.

12-minute read - Published 9:18 PM CEST, Tue September 15th, 2026

create a landscape of a connection being
new_hire_poster_bonito.png

This project is intended to showcase a fan-out, event-driven onboarding architecture built on Google Cloud. Pub/Sub acts as the central event distribution layer, allowing a single new-hire event to trigger multiple independent workflows. Cloud Run processes the serverless application logic, while Eventarc handles event routing for the IAM provisioning workflow. The solution automates both employee welcome communication and controlled access provisioning, with Secret Manager used for sensitive credentials and Cloud Storage supporting onboarding assets and dead-letter message persistence. The infrastructure is reconstructed and managed with Terraform, following Infrastructure as Code (IaC) practices, while GitHub Actions provides automated Terraform validation as part of the CI workflow.

Table of Contents:

Introduction:



    Brief overview of the project

AWS services used:

    Pub/Sub

    Cloud Run

    Eventarc

    IAM

    Secret Manager

    Cloud Storage

    Terraform

    GitHub Actions

    Python

    Gmail API

Code snippets:



    Examples of code snippets used in the project


    Explanation of each snippet and its purpose

Challenges faced:



    Detailed description of the challenges encountered during the project development


    How these challenges were overcome

Lessons learned:



    Key takeaways from the project


    How these lessons can be applied to future projects

Conclusion:



    Recap of the project and its outcomes


    Future plans and potential improvements

Project Overview - Diagram

This project demonstrates a serverless, event-driven onboarding platform built on Google Cloud and managed with Terraform.

The architecture includes:

New Hire Event: Starts the onboarding process.

Pub/Sub: Acts as the fan-out layer, distributing one event to multiple workflows.

Cloud Run: Runs the welcome-email and IAM provisioning services.

Eventarc: Routes events to the IAM provisioning workflow.

IAM: Controls service identities and employee access provisioning.

Secret Manager: Stores Gmail OAuth credentials securely.

Cloud Storage: Stores onboarding assets and DLQ messages.

Terraform: Manages the infrastructure as code.

GitHub Actions: Validates Terraform automatically through CI.

The application flow is as follows:

A new hire event is published to Pub/Sub.

Pub/Sub fans the event out to independent onboarding workflows.

The welcome-email workflow runs on Cloud Run and sends the onboarding email through the Gmail API.

The IAM provisioning workflow is routed through Eventarc to Cloud Run, where controlled roles are assigned.

Failed welcome-email events can be redirected to a DLQ and persisted in Cloud Storage.

The infrastructure is managed with Terraform, while GitHub Actions provides automated CI validation.

The project demonstrates serverless design, fan-out event-driven architecture, Infrastructure as Code, IAM automation, and secure cloud integration on Google Cloud.

Terraform Snippets (and urls)

image.png

import-core.sh

terraform.jpeg
terraform.jpeg

cloudrun.tf

ci.yml

terraform.jpeg

pubsub.tf

Code Stack Summary:

Defining the Github Actions Workflow (ci.yml):

Defining an event-driven infrastructure using GCP:

Defining an eventarc trigger that connects our Pub/Sub messages to Cloud run services using GCP:

Defining cloud run services to send a welcome email and role assignment. (Both services run in different Subs):

Defining our service accounts used in our event-driven architecture. For each service: role assignment and welcome email.

Defining our service accounts used in our event-driven architecture. For each service: role assignment and welcome email.

Further evidence that we are successfully logging messages sent to our DLQ.

image.png
image.png
Arrow_edited.png
Arrow_edited.png
Arrow_edited.png
Arrow_edited.png
Arrow_edited.png
image.png
Arrow_edited.png

    1. Managing an existing environment:

    Since I have already worked with production environments, I know that simply destroying resources and rebuilding everything is not a good default approach, especially when the infrastructure is already running and serving a purpose. Because of that, I wanted this project to reflect the same care I would expect in a real working environment. Instead of relying on destructive actions, I used Terraform imports, state reconciliation, and plan reviews to bring the existing GCP resources under Infrastructure as Code management. This made the process slower than starting from scratch, but it was also much more realistic and aligned with better operational practices.



    2. Understanding event-driven fan-out properly:

    Pub/Sub was probably where I learned the most architecturally. At first, it is easy to think of a topic and subscription as just another way of moving messages around, but throughout the project I started to understand how a single event can be published once and then fan out into independent workflows. The welcome-email path and IAM provisioning path were intentionally separated, which means each one can evolve, fail, or scale without tightly coupling the other. I think this was one of the strongest design decisions in the project.



    3. Eventarc and avoiding duplicate delivery paths:

    The Eventarc integration also taught me an important lesson. At one point, there was both a manually created Pub/Sub subscription and the Eventarc-managed subscription pointing toward the same logical workflow. That created unnecessary duplication and even contributed to concurrent IAM policy updates. Once I understood that Eventarc manages its own transport subscription, I removed the duplicate path and kept the architecture much cleaner. This was a good example of how understanding a managed service properly can simplify the architecture instead of adding more resources.



    4. IAM automation needs guardrails:

    Automating IAM changes was one of the parts I was most careful with. The application decides which roles should be assigned, but I did not want the application itself to have unrestricted permissions to modify anything in the project. To reduce that risk, I created a custom role and used an IAM Condition to limit which grants the service account is actually allowed to modify. I am happy with that separation because the application logic and the cloud authorization boundary reinforce each other instead of relying on code alone.

    One improvement I would make in a future iteration is adding explicit retry logic with exponential backoff for concurrent IAM policy updates. The project already exposed me to that type of concurrency problem, so that would be a natural next hardening step.



    5. Secrets should never become part of the codebase:

    The Gmail OAuth token was another part where I wanted to keep the implementation realistic. The credential is stored in Google Secret Manager instead of being hardcoded into Python or Terraform. Terraform manages only the secret container while the actual payload is injected separately, which keeps it out of Git, plan files, and Terraform state. This was a simple implementation decision, but it reinforced how important it is to separate infrastructure configuration from runtime credentials.



    6. Terraform drift is not always a real infrastructure change:

    After importing the resources, I noticed that Terraform could still report differences caused by provider-generated or Google-managed metadata, such as Cloud Run build information, image references, or automatically populated fields. That forced me to think more carefully about what Terraform should actually own. Instead of blindly trying to force every field into a zero-diff plan, I used lifecycle rules selectively and kept managed-service metadata outside the desired configuration where appropriate. That was a much better lesson than simply chasing a perfect-looking plan.



    7. CI was worth adding, even without full CD:

    Adding GitHub Actions near the end of the project made the repository feel much more complete. The workflow now automatically checks Terraform formatting, initialization, and validation on pushes and pull requests. I intentionally stopped at CI instead of rushing a full deployment pipeline just to say the project had CI/CD. The next step would be keyless deployment using OIDC and Workload Identity Federation, followed by Cloud Build, Artifact Registry, and potentially Cloud Deploy. I prefer having a smaller pipeline that actually works than a larger one added only for appearance.



    8. Keeping the project focused:

    One thing I think went particularly well was resisting the temptation to keep adding services just to make the architecture look more complex. The project already covers Pub/Sub, Eventarc, Cloud Run, IAM, Secret Manager, Cloud Storage, Terraform, and GitHub Actions, but every service has a clear reason to exist. If I continued evolving it, I would focus more on observability, automated testing, remote Terraform state, and controlled deployment rather than adding unrelated technologies.

After the message is published, the new hire receives a welcome email:

image.png

The email comes with a fully attached guide on how to survive day one (and who to reference to): 

image.png

User is automatically assigned to our Project and given its respective access, based on the message input (HR fills the form with the new hire's information, it is then assigned to the message and sent):

image.png

Finally, the user is now able to sign in to his account and to securely access to our GCP project. Do remember that it will have limited access to the project scope as only specific permissions were allowed in our double security system (IAM Conditions + custom cloud run script that assigns roles based on the user role informed in the message received)

End of part 5.

Throughout the course of this project, I encountered several challenges that strengthened my understanding of event-driven architecture, Infrastructure as Code, IAM security, and cloud operations on Google Cloud. Working with an existing environment, reconciling resources with Terraform, handling Pub/Sub and Eventarc behavior, and designing secure access boundaries all required careful troubleshooting and decision-making. Each challenge helped turn the project into a more realistic cloud engineering experience rather than just a simple lab.

Implementing keyless Continuous Deployment using GitHub OIDC and Google Cloud Workload Identity Federation;
Automating Cloud Run deployments through Cloud Build and Artifact Registry;
Adding Cloud Deploy for controlled and progressive releases, including canary deployments;
Moving Terraform state to a dedicated remote GCS backend;
Adding Terraform plan/apply workflows and automated drift detection;
Improving observability with Cloud Monitoring, log-based metrics, alerts, and DLQ monitoring;
Adding automated tests for both onboarding services;
Improving IAM policy update handling with retry logic and exponential backoff for concurrent changes;
Separating environments such as dev, staging, and production as the project evolves;
Expanding the fan-out model with additional onboarding workflows without changing the original event publisher.

Future plans:

bottom of page