mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-21 09:06:15 -06:00
[chore] update dependencies (#4423)
- codeberg.org/gruf/go-ffmpreg: v0.6.10 -> v0.6.11 - github.com/spf13/cast: v1.9.2 -> v1.10.0 - github.com/spf13/viper: v1.20.1 -> v1.21.0 - golang.org/x/crypto: v0.41.0 -> v0.42.0 - golang.org/x/image: v0.30.0 -> v0.31.0 Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4423 Co-authored-by: kim <grufwub@gmail.com> Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
parent
a6429b5410
commit
c949b9f2d1
97 changed files with 14611 additions and 3494 deletions
728
vendor/github.com/spf13/afero/README.md
generated
vendored
728
vendor/github.com/spf13/afero/README.md
generated
vendored
|
|
@ -1,442 +1,474 @@
|
|||

|
||||
|
||||
A FileSystem Abstraction System for Go
|
||||
|
||||
[](https://github.com/spf13/afero/actions/workflows/test.yml) [](https://godoc.org/github.com/spf13/afero) [](https://gitter.im/spf13/afero?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
# Overview
|
||||
|
||||
Afero is a filesystem framework providing a simple, uniform and universal API
|
||||
interacting with any filesystem, as an abstraction layer providing interfaces,
|
||||
types and methods. Afero has an exceptionally clean interface and simple design
|
||||
without needless constructors or initialization methods.
|
||||
|
||||
Afero is also a library providing a base set of interoperable backend
|
||||
filesystems that make it easy to work with, while retaining all the power
|
||||
and benefit of the os and ioutil packages.
|
||||
|
||||
Afero provides significant improvements over using the os package alone, most
|
||||
notably the ability to create mock and testing filesystems without relying on the disk.
|
||||
|
||||
It is suitable for use in any situation where you would consider using the OS
|
||||
package as it provides an additional abstraction that makes it easy to use a
|
||||
memory backed file system during testing. It also adds support for the http
|
||||
filesystem for full interoperability.
|
||||
<img src="https://cloud.githubusercontent.com/assets/173412/11490338/d50e16dc-97a5-11e5-8b12-019a300d0fcb.png" alt="afero logo-sm"/>
|
||||
|
||||
|
||||
## Afero Features
|
||||
[](https://github.com/spf13/afero/actions?query=workflow%3ACI)
|
||||
[](https://pkg.go.dev/mod/github.com/spf13/afero)
|
||||
[](https://goreportcard.com/report/github.com/spf13/afero)
|
||||

|
||||
|
||||
* A single consistent API for accessing a variety of filesystems
|
||||
* Interoperation between a variety of file system types
|
||||
* A set of interfaces to encourage and enforce interoperability between backends
|
||||
* An atomic cross platform memory backed file system
|
||||
* Support for compositional (union) file systems by combining multiple file systems acting as one
|
||||
* Specialized backends which modify existing filesystems (Read Only, Regexp filtered)
|
||||
* A set of utility functions ported from io, ioutil & hugo to be afero aware
|
||||
* Wrapper for go 1.16 filesystem abstraction `io/fs.FS`
|
||||
|
||||
# Using Afero
|
||||
# Afero: The Universal Filesystem Abstraction for Go
|
||||
|
||||
Afero is easy to use and easier to adopt.
|
||||
Afero is a powerful and extensible filesystem abstraction system for Go. It provides a single, unified API for interacting with diverse filesystems—including the local disk, memory, archives, and network storage.
|
||||
|
||||
A few different ways you could use Afero:
|
||||
Afero acts as a drop-in replacement for the standard `os` package, enabling you to write modular code that is agnostic to the underlying storage, dramatically simplifies testing, and allows for sophisticated architectural patterns through filesystem composition.
|
||||
|
||||
* Use the interfaces alone to define your own file system.
|
||||
* Wrapper for the OS packages.
|
||||
* Define different filesystems for different parts of your application.
|
||||
* Use Afero for mock filesystems while testing
|
||||
## Why Afero?
|
||||
|
||||
## Step 1: Install Afero
|
||||
Afero elevates filesystem interaction beyond simple file reading and writing, offering solutions for testability, flexibility, and advanced architecture.
|
||||
|
||||
First use go get to install the latest version of the library.
|
||||
🔑 **Key Features:**
|
||||
|
||||
$ go get github.com/spf13/afero
|
||||
* **Universal API:** Write your code once. Run it against the local OS, in-memory storage, ZIP/TAR archives, or remote systems (SFTP, GCS).
|
||||
* **Ultimate Testability:** Utilize `MemMapFs`, a fully concurrent-safe, read/write in-memory filesystem. Write fast, isolated, and reliable unit tests without touching the physical disk or worrying about cleanup.
|
||||
* **Powerful Composition:** Afero's hidden superpower. Layer filesystems on top of each other to create sophisticated behaviors:
|
||||
* **Sandboxing:** Use `CopyOnWriteFs` to create temporary scratch spaces that isolate changes from the base filesystem.
|
||||
* **Caching:** Use `CacheOnReadFs` to automatically layer a fast cache (like memory) over a slow backend (like a network drive).
|
||||
* **Security Jails:** Use `BasePathFs` to restrict application access to a specific subdirectory (chroot).
|
||||
* **`os` Package Compatibility:** Afero mirrors the functions in the standard `os` package, making adoption and refactoring seamless.
|
||||
* **`io/fs` Compatibility:** Fully compatible with the Go standard library's `io/fs` interfaces.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/spf13/afero
|
||||
```
|
||||
|
||||
Next include Afero in your application.
|
||||
```go
|
||||
import "github.com/spf13/afero"
|
||||
```
|
||||
|
||||
## Step 2: Declare a backend
|
||||
## Quick Start: The Power of Abstraction
|
||||
|
||||
First define a package variable and set it to a pointer to a filesystem.
|
||||
```go
|
||||
var AppFs = afero.NewMemMapFs()
|
||||
The core of Afero is the `afero.Fs` interface. By designing your functions to accept this interface rather than calling `os.*` functions directly, your code instantly becomes more flexible and testable.
|
||||
|
||||
or
|
||||
### 1. Refactor Your Code
|
||||
|
||||
var AppFs = afero.NewOsFs()
|
||||
```
|
||||
It is important to note that if you repeat the composite literal you
|
||||
will be using a completely new and isolated filesystem. In the case of
|
||||
OsFs it will still use the same underlying filesystem but will reduce
|
||||
the ability to drop in other filesystems as desired.
|
||||
|
||||
## Step 3: Use it like you would the OS package
|
||||
|
||||
Throughout your application use any function and method like you normally
|
||||
would.
|
||||
|
||||
So if my application before had:
|
||||
```go
|
||||
os.Open("/tmp/foo")
|
||||
```
|
||||
We would replace it with:
|
||||
```go
|
||||
AppFs.Open("/tmp/foo")
|
||||
```
|
||||
|
||||
`AppFs` being the variable we defined above.
|
||||
|
||||
|
||||
## List of all available functions
|
||||
|
||||
File System Methods Available:
|
||||
```go
|
||||
Chmod(name string, mode os.FileMode) : error
|
||||
Chown(name string, uid, gid int) : error
|
||||
Chtimes(name string, atime time.Time, mtime time.Time) : error
|
||||
Create(name string) : File, error
|
||||
Mkdir(name string, perm os.FileMode) : error
|
||||
MkdirAll(path string, perm os.FileMode) : error
|
||||
Name() : string
|
||||
Open(name string) : File, error
|
||||
OpenFile(name string, flag int, perm os.FileMode) : File, error
|
||||
Remove(name string) : error
|
||||
RemoveAll(path string) : error
|
||||
Rename(oldname, newname string) : error
|
||||
Stat(name string) : os.FileInfo, error
|
||||
```
|
||||
File Interfaces and Methods Available:
|
||||
```go
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.ReaderAt
|
||||
io.Seeker
|
||||
io.Writer
|
||||
io.WriterAt
|
||||
|
||||
Name() : string
|
||||
Readdir(count int) : []os.FileInfo, error
|
||||
Readdirnames(n int) : []string, error
|
||||
Stat() : os.FileInfo, error
|
||||
Sync() : error
|
||||
Truncate(size int64) : error
|
||||
WriteString(s string) : ret int, err error
|
||||
```
|
||||
In some applications it may make sense to define a new package that
|
||||
simply exports the file system variable for easy access from anywhere.
|
||||
|
||||
## Using Afero's utility functions
|
||||
|
||||
Afero provides a set of functions to make it easier to use the underlying file systems.
|
||||
These functions have been primarily ported from io & ioutil with some developed for Hugo.
|
||||
|
||||
The afero utilities support all afero compatible backends.
|
||||
|
||||
The list of utilities includes:
|
||||
Change functions that rely on the `os` package to accept `afero.Fs`.
|
||||
|
||||
```go
|
||||
DirExists(path string) (bool, error)
|
||||
Exists(path string) (bool, error)
|
||||
FileContainsBytes(filename string, subslice []byte) (bool, error)
|
||||
GetTempDir(subPath string) string
|
||||
IsDir(path string) (bool, error)
|
||||
IsEmpty(path string) (bool, error)
|
||||
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
SafeWriteReader(path string, r io.Reader) (err error)
|
||||
TempDir(dir, prefix string) (name string, err error)
|
||||
TempFile(dir, prefix string) (f File, err error)
|
||||
Walk(root string, walkFn filepath.WalkFunc) error
|
||||
WriteFile(filename string, data []byte, perm os.FileMode) error
|
||||
WriteReader(path string, r io.Reader) (err error)
|
||||
```
|
||||
For a complete list see [Afero's GoDoc](https://godoc.org/github.com/spf13/afero)
|
||||
// Before: Coupled to the OS and difficult to test
|
||||
// func ProcessConfiguration(path string) error {
|
||||
// data, err := os.ReadFile(path)
|
||||
// ...
|
||||
// }
|
||||
|
||||
They are available under two different approaches to use. You can either call
|
||||
them directly where the first parameter of each function will be the file
|
||||
system, or you can declare a new `Afero`, a custom type used to bind these
|
||||
functions as methods to a given filesystem.
|
||||
import "github.com/spf13/afero"
|
||||
|
||||
### Calling utilities directly
|
||||
|
||||
```go
|
||||
fs := new(afero.MemMapFs)
|
||||
f, err := afero.TempFile(fs,"", "ioutil-test")
|
||||
|
||||
```
|
||||
|
||||
### Calling via Afero
|
||||
|
||||
```go
|
||||
fs := afero.NewMemMapFs()
|
||||
afs := &afero.Afero{Fs: fs}
|
||||
f, err := afs.TempFile("", "ioutil-test")
|
||||
```
|
||||
|
||||
## Using Afero for Testing
|
||||
|
||||
There is a large benefit to using a mock filesystem for testing. It has a
|
||||
completely blank state every time it is initialized and can be easily
|
||||
reproducible regardless of OS. You could create files to your heart’s content
|
||||
and the file access would be fast while also saving you from all the annoying
|
||||
issues with deleting temporary files, Windows file locking, etc. The MemMapFs
|
||||
backend is perfect for testing.
|
||||
|
||||
* Much faster than performing I/O operations on disk
|
||||
* Avoid security issues and permissions
|
||||
* Far more control. 'rm -rf /' with confidence
|
||||
* Test setup is far more easier to do
|
||||
* No test cleanup needed
|
||||
|
||||
One way to accomplish this is to define a variable as mentioned above.
|
||||
In your application this will be set to afero.NewOsFs() during testing you
|
||||
can set it to afero.NewMemMapFs().
|
||||
|
||||
It wouldn't be uncommon to have each test initialize a blank slate memory
|
||||
backend. To do this I would define my `appFS = afero.NewOsFs()` somewhere
|
||||
appropriate in my application code. This approach ensures that Tests are order
|
||||
independent, with no test relying on the state left by an earlier test.
|
||||
|
||||
Then in my tests I would initialize a new MemMapFs for each test:
|
||||
```go
|
||||
func TestExist(t *testing.T) {
|
||||
appFS := afero.NewMemMapFs()
|
||||
// create test files and directories
|
||||
appFS.MkdirAll("src/a", 0755)
|
||||
afero.WriteFile(appFS, "src/a/b", []byte("file b"), 0644)
|
||||
afero.WriteFile(appFS, "src/c", []byte("file c"), 0644)
|
||||
name := "src/c"
|
||||
_, err := appFS.Stat(name)
|
||||
if os.IsNotExist(err) {
|
||||
t.Errorf("file \"%s\" does not exist.\n", name)
|
||||
}
|
||||
// After: Decoupled, flexible, and testable
|
||||
func ProcessConfiguration(fs afero.Fs, path string) error {
|
||||
// Use Afero utility functions which mirror os/ioutil
|
||||
data, err := afero.ReadFile(fs, path)
|
||||
// ... process the data
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
# Available Backends
|
||||
### 2. Usage in Production
|
||||
|
||||
## Operating System Native
|
||||
|
||||
### OsFs
|
||||
|
||||
The first is simply a wrapper around the native OS calls. This makes it
|
||||
very easy to use as all of the calls are the same as the existing OS
|
||||
calls. It also makes it trivial to have your code use the OS during
|
||||
operation and a mock filesystem during testing or as needed.
|
||||
In your production environment, inject the `OsFs` backend, which wraps the standard operating system calls.
|
||||
|
||||
```go
|
||||
appfs := afero.NewOsFs()
|
||||
appfs.MkdirAll("src/a", 0755)
|
||||
func main() {
|
||||
// Use the real OS filesystem
|
||||
AppFs := afero.NewOsFs()
|
||||
ProcessConfiguration(AppFs, "/etc/myapp.conf")
|
||||
}
|
||||
```
|
||||
|
||||
## Memory Backed Storage
|
||||
### 3. Usage in Testing
|
||||
|
||||
### MemMapFs
|
||||
|
||||
Afero also provides a fully atomic memory backed filesystem perfect for use in
|
||||
mocking and to speed up unnecessary disk io when persistence isn’t
|
||||
necessary. It is fully concurrent and will work within go routines
|
||||
safely.
|
||||
In your tests, inject `MemMapFs`. This provides a blazing-fast, isolated, in-memory filesystem that requires no disk I/O and no cleanup.
|
||||
|
||||
```go
|
||||
mm := afero.NewMemMapFs()
|
||||
mm.MkdirAll("src/a", 0755)
|
||||
func TestProcessConfiguration(t *testing.T) {
|
||||
// Use the in-memory filesystem
|
||||
AppFs := afero.NewMemMapFs()
|
||||
|
||||
// Pre-populate the memory filesystem for the test
|
||||
configPath := "/test/config.json"
|
||||
afero.WriteFile(AppFs, configPath, []byte(`{"feature": true}`), 0644)
|
||||
|
||||
// Run the test entirely in memory
|
||||
err := ProcessConfiguration(AppFs, configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### InMemoryFile
|
||||
## Afero's Superpower: Composition
|
||||
|
||||
As part of MemMapFs, Afero also provides an atomic, fully concurrent memory
|
||||
backed file implementation. This can be used in other memory backed file
|
||||
systems with ease. Plans are to add a radix tree memory stored file
|
||||
system using InMemoryFile.
|
||||
Afero's most unique feature is its ability to combine filesystems. This allows you to build complex behaviors out of simple components, keeping your application logic clean.
|
||||
|
||||
## Network Interfaces
|
||||
### Example 1: Sandboxing with Copy-on-Write
|
||||
|
||||
### SftpFs
|
||||
|
||||
Afero has experimental support for secure file transfer protocol (sftp). Which can
|
||||
be used to perform file operations over a encrypted channel.
|
||||
|
||||
### GCSFs
|
||||
|
||||
Afero has experimental support for Google Cloud Storage (GCS). You can either set the
|
||||
`GOOGLE_APPLICATION_CREDENTIALS_JSON` env variable to your JSON credentials or use `opts` in
|
||||
`NewGcsFS` to configure access to your GCS bucket.
|
||||
|
||||
Some known limitations of the existing implementation:
|
||||
* No Chmod support - The GCS ACL could probably be mapped to *nix style permissions but that would add another level of complexity and is ignored in this version.
|
||||
* No Chtimes support - Could be simulated with attributes (gcs a/m-times are set implicitly) but that's is left for another version.
|
||||
* Not thread safe - Also assumes all file operations are done through the same instance of the GcsFs. File operations between different GcsFs instances are not guaranteed to be consistent.
|
||||
|
||||
|
||||
## Filtering Backends
|
||||
|
||||
### BasePathFs
|
||||
|
||||
The BasePathFs restricts all operations to a given path within an Fs.
|
||||
The given file name to the operations on this Fs will be prepended with
|
||||
the base path before calling the source Fs.
|
||||
Create a temporary environment where an application can "modify" system files without affecting the actual disk.
|
||||
|
||||
```go
|
||||
bp := afero.NewBasePathFs(afero.NewOsFs(), "/base/path")
|
||||
// 1. The base layer is the real OS, made read-only for safety.
|
||||
baseFs := afero.NewReadOnlyFs(afero.NewOsFs())
|
||||
|
||||
// 2. The overlay layer is a temporary in-memory filesystem for changes.
|
||||
overlayFs := afero.NewMemMapFs()
|
||||
|
||||
// 3. Combine them. Reads fall through to the base; writes only hit the overlay.
|
||||
sandboxFs := afero.NewCopyOnWriteFs(baseFs, overlayFs)
|
||||
|
||||
// The application can now "modify" /etc/hosts, but the changes are isolated in memory.
|
||||
afero.WriteFile(sandboxFs, "/etc/hosts", []byte("127.0.0.1 sandboxed-app"), 0644)
|
||||
|
||||
// The real /etc/hosts on disk is untouched.
|
||||
```
|
||||
|
||||
### ReadOnlyFs
|
||||
### Example 2: Caching a Slow Filesystem
|
||||
|
||||
A thin wrapper around the source Fs providing a read only view.
|
||||
Improve performance by layering a fast cache (like memory) over a slow backend (like a network drive or cloud storage).
|
||||
|
||||
```go
|
||||
fs := afero.NewReadOnlyFs(afero.NewOsFs())
|
||||
_, err := fs.Create("/file.txt")
|
||||
// err = syscall.EPERM
|
||||
import "time"
|
||||
|
||||
// Assume 'remoteFs' is a slow backend (e.g., SFTP or GCS)
|
||||
var remoteFs afero.Fs
|
||||
|
||||
// 'cacheFs' is a fast in-memory backend
|
||||
cacheFs := afero.NewMemMapFs()
|
||||
|
||||
// Create the caching layer. Cache items for 5 minutes upon first read.
|
||||
cachedFs := afero.NewCacheOnReadFs(remoteFs, cacheFs, 5*time.Minute)
|
||||
|
||||
// The first read is slow (fetches from remote, then caches)
|
||||
data1, _ := afero.ReadFile(cachedFs, "data.json")
|
||||
|
||||
// The second read is instant (serves from memory cache)
|
||||
data2, _ := afero.ReadFile(cachedFs, "data.json")
|
||||
```
|
||||
|
||||
# RegexpFs
|
||||
### Example 3: Security Jails (chroot)
|
||||
|
||||
A filtered view on file names, any file NOT matching
|
||||
the passed regexp will be treated as non-existing.
|
||||
Files not matching the regexp provided will not be created.
|
||||
Directories are not filtered.
|
||||
Restrict an application component's access to a specific subdirectory.
|
||||
|
||||
```go
|
||||
fs := afero.NewRegexpFs(afero.NewMemMapFs(), regexp.MustCompile(`\.txt$`))
|
||||
_, err := fs.Create("/file.html")
|
||||
// err = syscall.ENOENT
|
||||
osFs := afero.NewOsFs()
|
||||
|
||||
// Create a filesystem rooted at /home/user/public
|
||||
// The application cannot access anything above this directory.
|
||||
jailedFs := afero.NewBasePathFs(osFs, "/home/user/public")
|
||||
|
||||
// To the application, this is reading "/"
|
||||
// In reality, it's reading "/home/user/public/"
|
||||
dirInfo, err := afero.ReadDir(jailedFs, "/")
|
||||
|
||||
// Attempts to access parent directories fail
|
||||
_, err = jailedFs.Open("../secrets.txt") // Returns an error
|
||||
```
|
||||
|
||||
### HttpFs
|
||||
## Real-World Use Cases
|
||||
|
||||
Afero provides an http compatible backend which can wrap any of the existing
|
||||
backends.
|
||||
### Build Cloud-Agnostic Applications
|
||||
|
||||
The Http package requires a slightly specific version of Open which
|
||||
returns an http.File type.
|
||||
|
||||
Afero provides an httpFs file system which satisfies this requirement.
|
||||
Any Afero FileSystem can be used as an httpFs.
|
||||
Write applications that seamlessly work with different storage backends:
|
||||
|
||||
```go
|
||||
httpFs := afero.NewHttpFs(<ExistingFS>)
|
||||
fileserver := http.FileServer(httpFs.Dir(<PATH>))
|
||||
http.Handle("/", fileserver)
|
||||
type DocumentProcessor struct {
|
||||
fs afero.Fs
|
||||
}
|
||||
|
||||
func NewDocumentProcessor(fs afero.Fs) *DocumentProcessor {
|
||||
return &DocumentProcessor{fs: fs}
|
||||
}
|
||||
|
||||
func (p *DocumentProcessor) Process(inputPath, outputPath string) error {
|
||||
// This code works whether fs is local disk, cloud storage, or memory
|
||||
content, err := afero.ReadFile(p.fs, inputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
processed := processContent(content)
|
||||
return afero.WriteFile(p.fs, outputPath, processed, 0644)
|
||||
}
|
||||
|
||||
// Use with local filesystem
|
||||
processor := NewDocumentProcessor(afero.NewOsFs())
|
||||
|
||||
// Use with Google Cloud Storage
|
||||
processor := NewDocumentProcessor(gcsFS)
|
||||
|
||||
// Use with in-memory filesystem for testing
|
||||
processor := NewDocumentProcessor(afero.NewMemMapFs())
|
||||
```
|
||||
|
||||
## Composite Backends
|
||||
### Treating Archives as Filesystems
|
||||
|
||||
Afero provides the ability have two filesystems (or more) act as a single
|
||||
file system.
|
||||
|
||||
### CacheOnReadFs
|
||||
|
||||
The CacheOnReadFs will lazily make copies of any accessed files from the base
|
||||
layer into the overlay. Subsequent reads will be pulled from the overlay
|
||||
directly permitting the request is within the cache duration of when it was
|
||||
created in the overlay.
|
||||
|
||||
If the base filesystem is writeable, any changes to files will be
|
||||
done first to the base, then to the overlay layer. Write calls to open file
|
||||
handles like `Write()` or `Truncate()` to the overlay first.
|
||||
|
||||
To writing files to the overlay only, you can use the overlay Fs directly (not
|
||||
via the union Fs).
|
||||
|
||||
Cache files in the layer for the given time.Duration, a cache duration of 0
|
||||
means "forever" meaning the file will not be re-requested from the base ever.
|
||||
|
||||
A read-only base will make the overlay also read-only but still copy files
|
||||
from the base to the overlay when they're not present (or outdated) in the
|
||||
caching layer.
|
||||
Read files directly from `.zip` or `.tar` archives without unpacking them to disk first.
|
||||
|
||||
```go
|
||||
base := afero.NewOsFs()
|
||||
layer := afero.NewMemMapFs()
|
||||
ufs := afero.NewCacheOnReadFs(base, layer, 100 * time.Second)
|
||||
import (
|
||||
"archive/zip"
|
||||
"github.com/spf13/afero/zipfs"
|
||||
)
|
||||
|
||||
// Assume 'zipReader' is a *zip.Reader initialized from a file or memory
|
||||
var zipReader *zip.Reader
|
||||
|
||||
// Create a read-only ZipFs
|
||||
archiveFS := zipfs.New(zipReader)
|
||||
|
||||
// Read a file from within the archive using the standard Afero API
|
||||
content, err := afero.ReadFile(archiveFS, "/docs/readme.md")
|
||||
```
|
||||
|
||||
### CopyOnWriteFs()
|
||||
### Serving Any Filesystem over HTTP
|
||||
|
||||
The CopyOnWriteFs is a read only base file system with a potentially
|
||||
writeable layer on top.
|
||||
|
||||
Read operations will first look in the overlay and if not found there, will
|
||||
serve the file from the base.
|
||||
|
||||
Changes to the file system will only be made in the overlay.
|
||||
|
||||
Any attempt to modify a file found only in the base will copy the file to the
|
||||
overlay layer before modification (including opening a file with a writable
|
||||
handle).
|
||||
|
||||
Removing and Renaming files present only in the base layer is not currently
|
||||
permitted. If a file is present in the base layer and the overlay, only the
|
||||
overlay will be removed/renamed.
|
||||
Use `HttpFs` to expose any Afero filesystem—even one created dynamically in memory—through a standard Go web server.
|
||||
|
||||
```go
|
||||
base := afero.NewOsFs()
|
||||
roBase := afero.NewReadOnlyFs(base)
|
||||
ufs := afero.NewCopyOnWriteFs(roBase, afero.NewMemMapFs())
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
fh, _ = ufs.Create("/home/test/file2.txt")
|
||||
fh.WriteString("This is a test")
|
||||
fh.Close()
|
||||
func main() {
|
||||
memFS := afero.NewMemMapFs()
|
||||
afero.WriteFile(memFS, "index.html", []byte("<h1>Hello from Memory!</h1>"), 0644)
|
||||
|
||||
// Wrap the memory filesystem to make it compatible with http.FileServer.
|
||||
httpFS := afero.NewHttpFs(memFS)
|
||||
|
||||
http.Handle("/", http.FileServer(httpFS.Dir("/")))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
```
|
||||
|
||||
In this example all write operations will only occur in memory (MemMapFs)
|
||||
leaving the base filesystem (OsFs) untouched.
|
||||
### Testing Made Simple
|
||||
|
||||
One of Afero's greatest strengths is making filesystem-dependent code easily testable:
|
||||
|
||||
## Desired/possible backends
|
||||
```go
|
||||
func SaveUserData(fs afero.Fs, userID string, data []byte) error {
|
||||
filename := fmt.Sprintf("users/%s.json", userID)
|
||||
return afero.WriteFile(fs, filename, data, 0644)
|
||||
}
|
||||
|
||||
The following is a short list of possible backends we hope someone will
|
||||
implement:
|
||||
func TestSaveUserData(t *testing.T) {
|
||||
// Create a clean, fast, in-memory filesystem for testing
|
||||
testFS := afero.NewMemMapFs()
|
||||
|
||||
userData := []byte(`{"name": "John", "email": "john@example.com"}`)
|
||||
err := SaveUserData(testFS, "123", userData)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("SaveUserData failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the file was saved correctly
|
||||
saved, err := afero.ReadFile(testFS, "users/123.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read saved file: %v", err)
|
||||
}
|
||||
|
||||
if string(saved) != string(userData) {
|
||||
t.Errorf("Data mismatch: got %s, want %s", saved, userData)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* SSH
|
||||
* S3
|
||||
**Benefits of testing with Afero:**
|
||||
- ⚡ **Fast** - No disk I/O, tests run in memory
|
||||
- 🔄 **Reliable** - Each test starts with a clean slate
|
||||
- 🧹 **No cleanup** - Memory is automatically freed
|
||||
- 🔒 **Safe** - Can't accidentally modify real files
|
||||
- 🏃 **Parallel** - Tests can run concurrently without conflicts
|
||||
|
||||
# About the project
|
||||
## Backend Reference
|
||||
|
||||
## What's in the name
|
||||
| Type | Backend | Constructor | Description | Status |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **Core** | **OsFs** | `afero.NewOsFs()` | Interacts with the real operating system filesystem. Use in production. | ✅ Official |
|
||||
| | **MemMapFs** | `afero.NewMemMapFs()` | A fast, atomic, concurrent-safe, in-memory filesystem. Ideal for testing. | ✅ Official |
|
||||
| **Composition** | **CopyOnWriteFs**| `afero.NewCopyOnWriteFs(base, overlay)` | A read-only base with a writable overlay. Ideal for sandboxing. | ✅ Official |
|
||||
| | **CacheOnReadFs**| `afero.NewCacheOnReadFs(base, cache, ttl)` | Lazily caches files from a slow base into a fast layer on first read. | ✅ Official |
|
||||
| | **BasePathFs** | `afero.NewBasePathFs(source, path)` | Restricts operations to a subdirectory (chroot/jail). | ✅ Official |
|
||||
| | **ReadOnlyFs** | `afero.NewReadOnlyFs(source)` | Provides a read-only view, preventing any modifications. | ✅ Official |
|
||||
| | **RegexpFs** | `afero.NewRegexpFs(source, regexp)` | Filters a filesystem, only showing files that match a regex. | ✅ Official |
|
||||
| **Utility** | **HttpFs** | `afero.NewHttpFs(source)` | Wraps any Afero filesystem to be served via `http.FileServer`. | ✅ Official |
|
||||
| **Archives** | **ZipFs** | `zipfs.New(zipReader)` | Read-only access to files within a ZIP archive. | ✅ Official |
|
||||
| | **TarFs** | `tarfs.New(tarReader)` | Read-only access to files within a TAR archive. | ✅ Official |
|
||||
| **Network** | **GcsFs** | `gcsfs.NewGcsFs(...)` | Google Cloud Storage backend. | ⚡ Experimental |
|
||||
| | **SftpFs** | `sftpfs.New(...)` | SFTP backend. | ⚡ Experimental |
|
||||
| **3rd Party Cloud** | **S3Fs** | [`fclairamb/afero-s3`](https://github.com/fclairamb/afero-s3) | Production-ready S3 backend built on official AWS SDK. | 🔹 3rd Party |
|
||||
| | **MinioFs** | [`cpyun/afero-minio`](https://github.com/cpyun/afero-minio) | MinIO object storage backend with S3 compatibility. | 🔹 3rd Party |
|
||||
| | **DriveFs** | [`fclairamb/afero-gdrive`](https://github.com/fclairamb/afero-gdrive) | Google Drive backend with streaming support. | 🔹 3rd Party |
|
||||
| | **DropboxFs** | [`fclairamb/afero-dropbox`](https://github.com/fclairamb/afero-dropbox) | Dropbox backend with streaming support. | 🔹 3rd Party |
|
||||
| **3rd Party Specialized** | **GitFs** | [`tobiash/go-gitfs`](https://github.com/tobiash/go-gitfs) | Git repository filesystem (read-only, Afero compatible). | 🔹 3rd Party |
|
||||
| | **DockerFs** | [`unmango/aferox`](https://github.com/unmango/aferox) | Docker container filesystem access. | 🔹 3rd Party |
|
||||
| | **GitHubFs** | [`unmango/aferox`](https://github.com/unmango/aferox) | GitHub repository and releases filesystem. | 🔹 3rd Party |
|
||||
| | **FilterFs** | [`unmango/aferox`](https://github.com/unmango/aferox) | Filesystem filtering with predicates. | 🔹 3rd Party |
|
||||
| | **IgnoreFs** | [`unmango/aferox`](https://github.com/unmango/aferox) | .gitignore-aware filtering filesystem. | 🔹 3rd Party |
|
||||
| | **FUSEFs** | [`JakWai01/sile-fystem`](https://github.com/JakWai01/sile-fystem) | Generic FUSE implementation using any Afero backend. | 🔹 3rd Party |
|
||||
|
||||
Afero comes from the latin roots Ad-Facere.
|
||||
## Afero vs. `io/fs` (Go 1.16+)
|
||||
|
||||
**"Ad"** is a prefix meaning "to".
|
||||
Go 1.16 introduced the `io/fs` package, which provides a standard abstraction for **read-only** filesystems.
|
||||
|
||||
**"Facere"** is a form of the root "faciō" making "make or do".
|
||||
Afero complements `io/fs` by focusing on different needs:
|
||||
|
||||
The literal meaning of afero is "to make" or "to do" which seems very fitting
|
||||
for a library that allows one to make files and directories and do things with them.
|
||||
* **Use `io/fs` when:** You only need to read files and want to conform strictly to the standard library interfaces.
|
||||
* **Use Afero when:**
|
||||
* Your application needs to **create, write, modify, or delete** files.
|
||||
* You need to test complex read/write interactions (e.g., renaming, concurrent writes).
|
||||
* You need advanced compositional features (Copy-on-Write, Caching, etc.).
|
||||
|
||||
The English word that shares the same roots as Afero is "affair". Affair shares
|
||||
the same concept but as a noun it means "something that is made or done" or "an
|
||||
object of a particular type".
|
||||
Afero is fully compatible with `io/fs`. You can wrap any Afero filesystem to satisfy the `fs.FS` interface using `afero.NewIOFS`:
|
||||
|
||||
It's also nice that unlike some of my other libraries (hugo, cobra, viper) it
|
||||
Googles very well.
|
||||
```go
|
||||
import "io/fs"
|
||||
|
||||
## Release Notes
|
||||
// Create an Afero filesystem (writable)
|
||||
var myAferoFs afero.Fs = afero.NewMemMapFs()
|
||||
|
||||
See the [Releases Page](https://github.com/spf13/afero/releases).
|
||||
// Convert it to a standard library fs.FS (read-only view)
|
||||
var myIoFs fs.FS = afero.NewIOFS(myAferoFs)
|
||||
```
|
||||
|
||||
## Third-Party Backends & Ecosystem
|
||||
|
||||
The Afero community has developed numerous backends and tools that extend the library's capabilities. Below are curated, well-maintained options organized by maturity and reliability.
|
||||
|
||||
### Featured Community Backends
|
||||
|
||||
These are mature, reliable backends that we can confidently recommend for production use:
|
||||
|
||||
#### **Amazon S3** - [`fclairamb/afero-s3`](https://github.com/fclairamb/afero-s3)
|
||||
Production-ready S3 backend built on the official AWS SDK for Go.
|
||||
|
||||
```go
|
||||
import "github.com/fclairamb/afero-s3"
|
||||
|
||||
s3fs := s3.NewFs(bucket, session)
|
||||
```
|
||||
|
||||
#### **MinIO** - [`cpyun/afero-minio`](https://github.com/cpyun/afero-minio)
|
||||
MinIO object storage backend providing S3-compatible object storage with deduplication and optimization features.
|
||||
|
||||
```go
|
||||
import "github.com/cpyun/afero-minio"
|
||||
|
||||
minioFs := miniofs.NewMinioFs(ctx, "minio://endpoint/bucket")
|
||||
```
|
||||
|
||||
### Community & Specialized Backends
|
||||
|
||||
#### Cloud Storage
|
||||
|
||||
- **Google Drive** - [`fclairamb/afero-gdrive`](https://github.com/fclairamb/afero-gdrive)
|
||||
Streaming support; no write-seeking or POSIX permissions; no files listing cache
|
||||
|
||||
- **Dropbox** - [`fclairamb/afero-dropbox`](https://github.com/fclairamb/afero-dropbox)
|
||||
Streaming support; no write-seeking or POSIX permissions
|
||||
|
||||
#### Version Control Systems
|
||||
|
||||
- **Git Repositories** - [`tobiash/go-gitfs`](https://github.com/tobiash/go-gitfs)
|
||||
Read-only filesystem abstraction for Git repositories. Works with bare repositories and provides filesystem view of any git reference. Uses go-git for repository access.
|
||||
|
||||
#### Container and Remote Systems
|
||||
|
||||
- **Docker Containers** - [`unmango/aferox`](https://github.com/unmango/aferox)
|
||||
Access Docker container filesystems as if they were local filesystems
|
||||
|
||||
- **GitHub API** - [`unmango/aferox`](https://github.com/unmango/aferox)
|
||||
Turn GitHub repositories, releases, and assets into browsable filesystems
|
||||
|
||||
#### FUSE Integration
|
||||
|
||||
- **Generic FUSE** - [`JakWai01/sile-fystem`](https://github.com/JakWai01/sile-fystem)
|
||||
Mount any Afero filesystem as a FUSE filesystem, allowing any Afero backend to be used as a real mounted filesystem
|
||||
|
||||
#### Specialized Filesystems
|
||||
|
||||
- **FAT32 Support** - [`aligator/GoFAT`](https://github.com/aligator/GoFAT)
|
||||
Pure Go FAT filesystem implementation (currently read-only)
|
||||
|
||||
### Interface Adapters & Utilities
|
||||
|
||||
**Cross-Interface Compatibility:**
|
||||
- [`jfontan/go-billy-desfacer`](https://github.com/jfontan/go-billy-desfacer) - Adapter between Afero and go-billy interfaces (for go-git compatibility)
|
||||
- [`Maldris/go-billy-afero`](https://github.com/Maldris/go-billy-afero) - Alternative wrapper for using Afero with go-billy
|
||||
- [`c4milo/afero2billy`](https://github.com/c4milo/afero2billy) - Another Afero to billy filesystem adapter
|
||||
|
||||
**Working Directory Management:**
|
||||
- [`carolynvs/aferox`](https://github.com/carolynvs/aferox) - Working directory-aware filesystem wrapper
|
||||
|
||||
**Advanced Filtering:**
|
||||
- [`unmango/aferox`](https://github.com/unmango/aferox) includes multiple specialized filesystems:
|
||||
- **FilterFs** - Predicate-based file filtering
|
||||
- **IgnoreFs** - .gitignore-aware filtering
|
||||
- **WriterFs** - Dump writes to io.Writer for debugging
|
||||
|
||||
#### Developer Tools & Utilities
|
||||
|
||||
**nhatthm Utility Suite** - Essential tools for Afero development:
|
||||
- [`nhatthm/aferocopy`](https://github.com/nhatthm/aferocopy) - Copy files between any Afero filesystems
|
||||
- [`nhatthm/aferomock`](https://github.com/nhatthm/aferomock) - Mocking toolkit for testing
|
||||
- [`nhatthm/aferoassert`](https://github.com/nhatthm/aferoassert) - Assertion helpers for filesystem testing
|
||||
|
||||
### Ecosystem Showcase
|
||||
|
||||
**Windows Virtual Drives** - [`balazsgrill/potatodrive`](https://github.com/balazsgrill/potatodrive)
|
||||
Mount any Afero filesystem as a Windows drive letter. Brilliant demonstration of Afero's power!
|
||||
|
||||
### Modern Asset Embedding (Go 1.16+)
|
||||
|
||||
Instead of third-party tools, use Go's native `//go:embed` with Afero:
|
||||
|
||||
```go
|
||||
import (
|
||||
"embed"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
//go:embed assets/*
|
||||
var assetsFS embed.FS
|
||||
|
||||
func main() {
|
||||
// Convert embedded files to Afero filesystem
|
||||
fs := afero.FromIOFS(assetsFS)
|
||||
|
||||
// Use like any other Afero filesystem
|
||||
content, _ := afero.ReadFile(fs, "assets/config.json")
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it
|
||||
We welcome contributions! The project is mature, but we are actively looking for contributors to help implement and stabilize network/cloud backends.
|
||||
|
||||
* 🔥 **Microsoft Azure Blob Storage**
|
||||
* 🔒 **Modern Encryption Backend** - Built on secure, contemporary crypto (not legacy EncFS)
|
||||
* 🐙 **Canonical go-git Adapter** - Unified solution for Git integration
|
||||
* 📡 **SSH/SCP Backend** - Secure remote file operations
|
||||
* Stabilization of existing experimental backends (GCS, SFTP)
|
||||
|
||||
To contribute:
|
||||
1. Fork the repository
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create new Pull Request
|
||||
5. Create a new Pull Request
|
||||
|
||||
## Contributors
|
||||
## 📄 License
|
||||
|
||||
Names in no particular order:
|
||||
Afero is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/afero/blob/master/LICENSE.txt) for details.
|
||||
|
||||
* [spf13](https://github.com/spf13)
|
||||
* [jaqx0r](https://github.com/jaqx0r)
|
||||
* [mbertschler](https://github.com/mbertschler)
|
||||
* [xor-gate](https://github.com/xor-gate)
|
||||
## 🔗 Additional Resources
|
||||
|
||||
## License
|
||||
- [📖 Full API Documentation](https://pkg.go.dev/github.com/spf13/afero)
|
||||
- [🎯 Examples Repository](https://github.com/spf13/afero/tree/master/examples)
|
||||
- [📋 Release Notes](https://github.com/spf13/afero/releases)
|
||||
- [❓ GitHub Discussions](https://github.com/spf13/afero/discussions)
|
||||
|
||||
Afero is released under the Apache 2.0 license. See
|
||||
[LICENSE.txt](https://github.com/spf13/afero/blob/master/LICENSE.txt)
|
||||
---
|
||||
|
||||
*Afero comes from the Latin roots Ad-Facere, meaning "to make" or "to do" - fitting for a library that empowers you to make and do amazing things with filesystems.*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue