Use a go-billy filesystem¶
go-billy is the filesystem abstraction go-git
is built on, so a tool that already holds a billy.Filesystem — an in-memory worktree, a chroot, an
on-disk root — can point config at it through the
config-billy sibling module.
import (
"github.com/go-git/go-billy/v5/osfs"
"gitlab.com/phpboyscout/go/config"
configbilly "gitlab.com/phpboyscout/go/config-billy"
)
bfs := osfs.New("/etc/app") // or an in-memory memfs, a chroot, a worktree
store, err := config.NewStore(ctx,
config.WithFiles(configbilly.Wrap(bfs), "config.yaml"),
config.WithEnv("APP"),
)
Wrap is the whole API, and the layer reads and writes — a store.Apply commits back into the
billy filesystem through its native rename, so an edit lands atomically and structure is preserved.
Paths are forward-slash and relative to the filesystem's root, like config.Dir and unlike
config.OS(): an osfs.New("/etc/app") reads "config.yaml", not "/etc/app/config.yaml".
When you do not need this¶
If the file is on local disk, config.OS() or config.Dir(path) already cover it.
Reach for config-billy when something upstream handed you a billy filesystem and you
have to pass it through — most often a go-git worktree, in-memory or on disk, where the
configuration lives inside the repository you are operating on.
Read-only billy filesystems¶
A billy.Filesystem can be mounted without write capability. When it is, config-billy maps that to
the shared sentinel config.ErrReadOnlyFS up front, so a read-only filesystem gives a consistent
errors.Is story whether it is read-only by design or by how it was mounted:
_, err := store.Apply(ctx, config.Set("server.port", 9090))
if errors.Is(err, config.ErrReadOnlyFS) {
// the wrapped billy filesystem reports no WriteCapability
}
Symlinks¶
billy.Filesystem mandates Readlink, so config-billy satisfies config.LinkReader for every
wrapped filesystem — a config file reached through a symlink is written through it, rather than
having the link replaced with a regular file. Where a path is not a link, the filesystem says so and
the path is used as given.
What it costs¶
| Modules added | 10 — 1 for go-billy, 9 for the config graph |
go-billy, config, and what those two already bring — asserted by an allowlist test in the module,
so an unforeseen transitive addition fails its build rather than arriving quietly.
Related¶
- How filesystem adapters work — the
config.FSfamily - Use an afero filesystem — the equivalent bridge for an
afero.Fs - Backends & capabilities — the
config.FSinterface andLinkReader