Skip to main content

State

Terraform records what exists in the cloud. That is enough to plan a change and not enough to explain one: a plan that recreates a bucket cannot say that the request behind it was "checkout keeps receipts", because nothing in Terraform's state ever knew.

Octo keeps its own record, in three files under .octo/. They hold what you asked for, what exists, and the link between the two, and they are what turns a line in a plan into a sentence about your infrastructure.

Commit them. This is not a cache.

The three files​

.octo/
├── models.json your intent, as it was last applied
├── resources-actual.json what Octo believes exists in the cloud
└── resources-old.json the previous resource graph

models.json is the model graph: every model, anchor, overlay, and edge between them, each model and overlay with the module that produced it, and each model with the base model it extends. The shop's receipts service is in there as a service in the region, with the bucket name somebody typed and the access prefix it was told to declare. Beside the graph, it lists every module that ran, with the inputs octo.yaml gave it.

resources-actual.json is what Octo believes exists. Here is the receipts bucket, after an apply and a commit:

"@octo/s3-storage=bucket-shop-receipts": {
"className": "@octo/S3Storage",
"createdBy": "service=shop-receipts-s3-storage,region=eu-west-1,account=123456789012,app=shop",
"kind": "terraform",
"models": ["service=shop-receipts-s3-storage,region=eu-west-1,account=123456789012,app=shop"],
"moduleId": "receipts-module",
"resource": {
"parents": [
{
"context": "@octo/iam-role=iam-role-ServerRole-checkout"
}
],
"properties": {
"awsAccountId": "123456789012",
"awsRegionId": "eu-west-1",
"Bucket": "shop-receipts",
"permissions": [
{
"accessPrefix": "uploads",
"allowRead": true,
"allowWrite": true,
"principalResourceId": "iam-role-ServerRole-checkout",
}
]
},
"resourceId": "bucket-shop-receipts",
"response": {
"Arn": "arn:aws:s3:::shop-receipts"
},
"tags": {}
}
}

The first lines say where the bucket came from: the receipts service's action created it, in the receipts module, and Terraform owns it. models lists every model and overlay whose action returned the bucket; here that is the service alone. A resource Octo creates itself, through an action of its own rather than through Terraform, is external.

Checkout's grant is in there twice over: as the permission the bucket carries, and as the parent edge that puts the role before the bucket.

The response is the part only a real apply can produce. Before the first commit it is empty; afterwards it holds the ARN, and that is what any other resource reads when it needs to point at this bucket.

The same file records which Terraform addresses each resource rendered into. That is the whole trick behind a report: Terraform says aws_s3_bucket_policy.bucket-shop-receipts-policy changed, and this record says that address belongs to the receipts bucket, which belongs to the receipts service, which is in your intent. An address that appears in a plan and in nobody's list is drift.

resources-old.json is the previous resource graph, kept so a re-run can tell a settled state from one where a change was started and not finished.

Two states, two jobs​

RecordsWritten byLives in
Octo stateYour intent, your resources, and the link between themocto generate and octo commit.octo/
Terraform stateCloud objects and their attributesterraform apply.octo/<moduleId>.tfstate

Neither replaces the other. Terraform state is what lets Terraform know a bucket exists. Octo state is what lets a report say the bucket is checkout's receipts storage.

Both need to survive, and both go through the same state provider. Terraform writes its file into the module folder as it always has; Octo checks that file out before every Terraform command and back in after, so whoever can reach Octo state can reach Terraform state with it.

When it is written​

StepModel stateResource state
generatewritten
validate
apply
commitwrittenwritten

generate saves the model graph because the run has established what your intent is. Resource state waits for commit, because until an apply has happened there is nothing to record. What Octo Saves and When follows every command, Terraform's state and the lock included.

Commit is all or nothing. If an expected Terraform output is missing, it throws before touching any file rather than writing half a record, so a commit that failed can be re-run once you have fixed what made the apply incomplete.

Where it is kept​

A state provider decides where these files live, and holds the lock that stops two runs writing at once. Three ship with Octo, chosen in the definition file:

settings:
stateProvider:
type: LocalStateProvider
statePath: .octo

LocalStateProvider writes plain JSON to a directory, which is what the shop uses. State Providers covers the other two, and which one a team needs.

Losing it​

Deleting .octo/ does nothing to your cloud account and everything to what Octo and Terraform know about it, because both records are in there. The next octo generate finds no prior state, treats the run as a first run, and proposes creating your entire infrastructure again. Terraform, with no state of its own left to object, agrees.

The apply that follows fails on whatever is unique: a bucket name already taken, a role that already exists. Whatever is not unique it builds a second time without complaint, so the shop would gain a second VPC in the same apply that failed on its bucket. Recovering means restoring the files before anything applies, which is the argument for keeping them somewhere they cannot be lost rather than for having a recovery procedure.