Repository navigation
feat: lock the OpenTofu state of a building block while tfstate exec runs tofu - #26
Merged
Merged
Conversation
grubmeshi
force-pushed
the
feature/tfstate-locking
branch
from
October 5, 2026 11:24
e9ea3b2 to
35ba0eb
Compare
grubmeshi
force-pushed
the
feature/tfstate-locking
branch
3 times, most recently
from
October 6, 2026 18:01
a7d5e53 to
820763a
Compare
|
Coverage of the acceptance run against the meshStack backend, on
Uncovered functions
|
grubmeshi
force-pushed
the
feature/tfstate-locking
branch
3 times, most recently
from
October 7, 2026 08:49
d79568e to
82e662c
Compare
The package now sits below the command it belongs to, and is named after its subcommand. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
grubmeshi
force-pushed
the
feature/tfstate-locking
branch
3 times, most recently
from
October 7, 2026 09:45
305d967 to
68a91ab
Compare
grubmeshi
commented
Oct 7, 2026
grubmeshi
commented
Oct 7, 2026
grubmeshi
commented
Oct 7, 2026
grubmeshi
commented
Oct 7, 2026
grubmeshi
commented
Oct 7, 2026
grubmeshi
commented
Oct 7, 2026
grubmeshi
commented
Oct 7, 2026
…than in bb tfstate The commands stay front ends: which workspace holds the state, and whether a run of the building block writes it as well, are internal/tfstate's to decide. exec adds only the hint to --force. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
meshStack grants access by the workspace the login works in, not by a workspace in the path, so a bare 403 did not say why. Every 403 now names the profile, and for a browser login the workspace it works in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…front end http.NewClient takes an http.UserAgent, which holds the GitHubRepo and Version that ResolveSessionOptions had as fields of its own, and refuses one that does not validate. ResolveSessionOptions embeds it, so a front end still sets GitHubRepo and Version in the options literal, and the release check reads the same two. client.New takes the session's http.Client in place of a user agent string. The API docs download through a client from cmd/internal's options, rather than naming the CLI a second time. forbidigo keeps http.UserAgent to those options, so that no request leaves without the front end's name, or with one that drifts from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…evelopment skill AGENTS.md is loaded into every session, while the package layout, the command tree, the rules of client/ and internal/http, and the settings matter only while Go code changes. The skill also states that a command is a front end over an internal/ package, and points to the modern-go-guidelines plugin for Go idioms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tfstate exec now points tofu's lock calls at its proxy. In --mode readwrite the proxy passes them on to meshStack, so a run of the building block waits while tofu holds the lock, and a second exec fails with the holder's lock info. The proxy sends tofu's lock ID with every write, as meshStack refuses a write without it while a lock is held. A write that meshStack refuses for another holder's lock goes back to tofu as the 423 it is, and the log names the holder, because tofu prints only the status code. A 5xx of meshStack goes to tofu as well, which retries it, and is logged rather than failing exec. In --mode read the proxy answers the lock calls itself, so a plan never makes a run wait. --force now also stores a state that does not follow the stored one, so that tofu state push -force gets through. exec warns when tofu has held the lock for 5 minutes, the time a waiting run waits before it fails. Workspace Owner and Workspace Manager now have the MANAGED_TFSTATE rights, so the help no longer sends every user to an API key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…releases tofu force-unlock needs a module directory with the backend and the lock ID. force-unlock needs only the building block: it reads the lock from meshStack, names its holder, and releases it by the lock ID it found, so a lock taken in the meantime stays. It refuses to release the lock of a run that is pending or in progress, unless --force. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uite bootstraps The building blocks of the local stack are manual ones and never reach the tf-block-runner. TestAccTfstate applies a tofu config that creates a workspace, binds a test user as Workspace Manager, and orders the noop building block of meshstack-hub, which the tf-block-runner applies with meshStack's state backend. It then runs real tofu through exec in both modes, checks that a second exec and a run wait for the lock that an exec holds, that force-unlock releases a lock left behind, and that the Workspace Manager's browser login reads, locks and writes the state. It destroys what it created at the end. The tests that picked a building block of the local stack move to the bootstrapped one. They fail when tofu is not on the PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the CLI's login The proxy of exec now passes every request outside the state on to the meshStack API, with the session's token in place of the one the command sent, so that tofu's meshstack provider works with the CLI's login, which the CLI renews. The command gets MESHSTACK_ENDPOINT of the proxy and a MESHSTACK_API_TOKEN that only the proxy accepts, so any provider version works, v0.24.5 included, as long as the provider block sets no endpoint or credentials. --mode read passes only GET and HEAD on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-backup-dir names exec --mode readwrite no longer copies the stored state into the configuration directory before each write. With --backup-dir it copies it into that directory, which it creates with mode 0700, as files with mode 0600, since a state can hold secrets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A building block's module has no backend block, since the runner adds one only while it runs, and OpenTofu cannot take the backend type from the environment. With --override-backend, exec writes zz_meshstack_override.tf into the working directory while the command runs. As an override file it adds the http backend to a module without one, and replaces the module's own backend. exec refuses to replace a file of that name. The help and the warning for a command that asked for no state now point to the flag rather than to a backend file the user adds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
grubmeshi
force-pushed
the
feature/tfstate-locking
branch
from
October 7, 2026 11:24
68a91ab to
0bf620c
Compare
grubmeshi
marked this pull request as ready for review
October 7, 2026 11:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two writers must not change the same OpenTofu state in meshStack at the same time: runs of the
tf-block-runner, and people (or AI agents) who use
meshstack bb tfstate exec --mode readwritetodebug or repair a building block's state. meshStack now locks the state (meshfed-release#11278) and
the tf-block-runner takes that lock (building-block-runner#77); both are merged. This PR makes the
CLI take part in the lock, and makes
tfstate execusable for cleanups and repairs with tofu'smeshstack provider.
What changes for users
tfstate exec --mode readwriteholds meshStack's lock while tofu runs. A run of the blockwaits for it; a second exec fails and names the holder.
--mode readnever blocks a run.meshstack bb tfstate force-unlock <uuid>shows who holds the lock and releases it afterthe user types
yes. It refuses a lock of a pending or running run unless--force.execwith the CLI's login, any provider version,as long as the provider block sets no endpoint or credentials. In
--mode readthe provider canonly read.
tfstate exec --override-backendadds the http backend while the command runs, so a module needs no backend file.tfstate exec --mode readwritekeeps backups only with--backup-dir <dir>. It no longercopies the stored state into the configuration directory before each write; with the flag it
writes each copy into
<dir>with mode 0600.How to review
The commits are meant to be read one by one; each message explains its part.
refactor: move the bb tfstate commands to cmd/buildingblock/tfstaterefactor: check the building block's runs in internal/tfstate …internal/tfstate:OpenStore,NoRunOfchore: ignore scratch/ …fix: name the auth scope when meshStack answers 403Authorization.Scope(), and the wrap of every 403 inAuthorizedClient.DoRequestrefactor(client): build every HTTP client with the user agent of its front endhttp.UserAgentembedded inResolveSessionOptions, so the provider's options literal compiles unchanged;client.Newnow takes anhttp.Clientdocs: move the notes on how the code is built from AGENTS.md into a development skillinternal/rule the skill addsfeat: lock the state in meshStack while tofu runs under bb tfstate execfeat: add meshstack bb tfstate force-unlock …test: run tofu against the state of a building block the acceptance suite bootstrapsfeat: let the command of bb tfstate exec reach the meshStack API with the CLI's logininternal/tfstate/apiproxy, the one package besidesinternal/httpthat touches the transport, and the User-Agent it forwardsfeat: save state backups of bb tfstate exec only into the directory --backup-dir namesfeat: let bb tfstate exec add the http backend with --override-backendThe acceptance tests need
tofuon the PATH and run in meshfed-release's satellite CI.Related PRs
endpoints, and the
MANAGED_TFSTATE_*rights for Workspace Owner and Manager.🤖 Generated with Claude Code