FinOps - User Offboarding
Automate cloud resource deprovisioning based on user lifecycle changes to eliminate waste.
Automating the Lifecycle of User-Bound Resources
A common source of cloud waste comes from resources that are provisioned for a specific user but are not properly decommissioned when that user leaves the company. These “orphaned” resources, such as developer VMs, test databases, or personal dashboards, continue to incur costs long after they are needed.
This example walks through a concrete scenario of managing the lifecycle of cloud resources tied to users. We’ll start with a list of users and a model that provisions a cloud virtual machine (VM) for each active user. This will demonstrate how a simple change in a single source of truth—the user list—can automatically and safely trigger decommissioning, and how compliance rules can enforce data retention policies.
1. User Onboarding and Provisioning
Our single source of truth for users is a simple CSV file, which could be an export from an HR system.
data/assets/user.csv
name,email,role,active
alice,alice@example.com,developer,true
bob,bob@example.com,developer,true
carol,carol@example.com,manager,true
The architectural model dictates that every active user is assigned a cloud_vm. This is our “recipe” for provisioning.
data/models/vm.toml
origin_resource = "user"
[[create_resource]]
# Only create a VM for users where 'active' is true.
match_on = [
{ property = "active", value = true }
]
resource_type = "cloud_vm"
relation_type = "HAS_VM"
name = "vm-for-{{ origin_resource.name }}"
[create_resource.properties]
owner_email = "{{ origin_resource.email }}"
size = "t3.medium"
When we run rescile, it generates a graph where Alice, Bob, and Carol each have a corresponding cloud_vm. We can save this state as our “before” snapshot:
# Generate the initial graph and save it
rescile-ce save before.json
2. User Offboarding and Automated Decommissioning
Now, user Bob leaves the company. The offboarding process simply involves updating the user.csv file to mark Bob as inactive.
data/assets/user.csv (change)
--- a/data/assets/user.csv
+++ b/data/assets/user.csv
@@ -2,5 +2,5 @@
alice,alice@example.com,developer,true
-bob,bob@example.com,developer,true
+bob,bob@example.com,developer,false
carol,carol@example.com,manager,true
With this single change, the recipe for our environment has been updated. The vm.toml model’s match_on condition no longer holds true for Bob’s user record, so vm-for-bob will not be part of the desired state.
We can visualize this change by generating a new graph and comparing it to our “before” snapshot using rescile-ce diff. This diff isn’t just a report; it’s an executable plan for decommissioning.
# Compare the current state against the previous one
rescile-ce diff before.json
Result:
Structural Graph Comparison Result
Resources Removed:
(-) vm-for-bob (type: cloud_vm)
Relations Removed:
(-) (user `bob`) --[HAS_VM]--> (cloud_vm `vm-for-bob`)
The output confirms that the only structural change is the removal of Bob’s VM and its relationship to him. Automation can now safely deprovision vm-for-bob with full confidence that it’s no longer required, preventing orphaned resources and wasted spend.
3. Enforcing Data Retention Policies with Compliance-as-Code
Building on this, we can enforce organizational policies across the resource lifecycle. Let’s add a compliance requirement: all VMs belonging to inactive users must be kept for 90 days before deletion. This policy is defined declaratively in a compliance file.
data/compliance/retention.toml
audit_id = "LIFECYCLE-POLICY"
audit_name = "Resource Lifecycle Management"
[[control]]
id = "RETENTION-1"
name = "Default VM Retention Policy"
[control.config]
retention_days = 90
[[control.target]]
description = "Set a 90-day retention to all inactive cloud VMs."
origin_resource_type = "user"
match_on = [
{ property = "active", value = false }
]
# For each 'user', create or update 'cloud_vm' resource.
[control.target.resource]
type = "cloud_vm"
name = "vm-for-{{ origin_resource.name }}"
[control.target.resource.properties]
[control.target.relation]
type = "HAD_VM"
properties_from_config = ["retention_days"]
We can visualize this change by generating a new graph and comparing it to our “before” snapshot using rescile-ce diff.
# Compare the two states
rescile-ce diff before.json
The diff command produces a clear, human-readable summary of what has changed, serving as a perfect plan for decommissioning.
Structural Graph Comparison Result
Resources Added:
(+) LIFECYCLE-POLICY (type: audit)
(+) RETENTION-1 (type: control)
Relations Added:
(+) (cloud_vm `vm-for-bob`) --[VALIDATED_BY]--> (control `RETENTION-1`)
(+) (control `RETENTION-1`) --[BELONGS_TO]--> (audit `LIFECYCLE-POLICY`)
(+) (user `bob`) --[HAD_VM]--> (cloud_vm `vm-for-bob`)
Relations Removed:
(-) (user `bob`) --[HAS_VM]--> (cloud_vm `vm-for-bob`)
After running rescile, Bob’s cloud_vm node in the graph will now have a retention_days: 90 property. This makes the retention policy an auditable, structural part of the environment’s Digital Twin, not just a line in a wiki. An automation script or reporting tool can now query the graph to find all resources approaching their deletion date, turning a manual audit process into a proactive, automated workflow.