Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filesystem: Globally declared filesystems, fs directive #5833

Merged
merged 10 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions caddy.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/google/uuid"
"go.uber.org/zap"

"github.com/caddyserver/caddy/v2/internal/filesystems"
"github.com/caddyserver/caddy/v2/notify"
)

Expand Down Expand Up @@ -84,6 +85,9 @@ type Config struct {
storage certmagic.Storage

cancelFunc context.CancelFunc

// filesystems is a dict of filesystems that will later be loaded from and added to.
filesystems FileSystems
}

// App is a thing that Caddy runs.
Expand Down Expand Up @@ -447,6 +451,9 @@ func run(newCfg *Config, start bool) (Context, error) {
}
}

// create the new filesystem map
newCfg.filesystems = &filesystems.FilesystemMap{}

// prepare the new config for use
newCfg.apps = make(map[string]App)

Expand Down
2 changes: 1 addition & 1 deletion caddyconfig/caddyfile/dispenser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func TestDispenser_ArgErr_Err(t *testing.T) {
t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
}

var ErrBarIsFull = errors.New("bar is full")
ErrBarIsFull := errors.New("bar is full")
bookingError := d.Errf("unable to reserve: %w", ErrBarIsFull)
if !errors.Is(bookingError, ErrBarIsFull) {
t.Errorf("Errf(): should be able to unwrap the error chain")
Expand Down
18 changes: 9 additions & 9 deletions caddyconfig/caddyfile/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
)

func TestParseVariadic(t *testing.T) {
var args = make([]string, 10)
args := make([]string, 10)
for i, tc := range []struct {
input string
result bool
Expand Down Expand Up @@ -111,7 +111,6 @@ func TestAllTokens(t *testing.T) {
input := []byte("a b c\nd e")
expected := []string{"a", "b", "c", "d", "e"}
tokens, err := allTokens("TestAllTokens", input)

if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
Expand Down Expand Up @@ -149,10 +148,11 @@ func TestParseOneAndImport(t *testing.T) {
"localhost",
}, []int{1}},

{`localhost:1234
{
`localhost:1234
dir1 foo bar`, false, []string{
"localhost:1234",
}, []int{3},
"localhost:1234",
}, []int{3},
},

{`localhost {
Expand Down Expand Up @@ -407,13 +407,13 @@ func TestRecursiveImport(t *testing.T) {
err = os.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import recursive_import_test2`), 0644)
import recursive_import_test2`), 0o644)
if err != nil {
t.Fatal(err)
}
defer os.Remove(recursiveFile1)

err = os.WriteFile(recursiveFile2, []byte("dir2 1"), 0644)
err = os.WriteFile(recursiveFile2, []byte("dir2 1"), 0o644)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -441,7 +441,7 @@ func TestRecursiveImport(t *testing.T) {
err = os.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import `+recursiveFile2), 0644)
import `+recursiveFile2), 0o644)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -495,7 +495,7 @@ func TestDirectiveImport(t *testing.T) {
}

err = os.WriteFile(directiveFile, []byte(`prop1 1
prop2 2`), 0644)
prop2 2`), 0o644)
if err != nil {
t.Fatal(err)
}
Expand Down
18 changes: 18 additions & 0 deletions caddyconfig/httpcaddyfile/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
func init() {
RegisterDirective("bind", parseBind)
RegisterDirective("tls", parseTLS)
RegisterHandlerDirective("fs", parseFilesystem)
RegisterHandlerDirective("root", parseRoot)
RegisterHandlerDirective("vars", parseVars)
RegisterHandlerDirective("redir", parseRedir)
Expand Down Expand Up @@ -658,6 +659,23 @@ func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
return caddyhttp.VarsMiddleware{"root": root}, nil
}

// parseFilesystem parses the fs directive. Syntax:
//
// fs <filesystem>
func parseFilesystem(h Helper) (caddyhttp.MiddlewareHandler, error) {
var name string
for h.Next() {
if !h.NextArg() {
return nil, h.ArgErr()
}
name = h.Val()
if h.NextArg() {
return nil, h.ArgErr()
}
}
return caddyhttp.VarsMiddleware{"fs": name}, nil
}

// parseVars parses the vars directive. See its UnmarshalCaddyfile method for syntax.
func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) {
v := new(caddyhttp.VarsMiddleware)
Expand Down
1 change: 1 addition & 0 deletions caddyconfig/httpcaddyfile/directives.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var directiveOrder = []string{

"map",
"vars",
"fs",
"root",
"skip_log",

Expand Down
9 changes: 6 additions & 3 deletions caddyconfig/httpcaddyfile/directives_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,23 @@ func TestHostsFromKeys(t *testing.T) {
[]Address{
{Original: ":2015", Port: "2015"},
},
[]string{}, []string{},
[]string{},
[]string{},
},
{
[]Address{
{Original: ":443", Port: "443"},
},
[]string{}, []string{},
[]string{},
[]string{},
},
{
[]Address{
{Original: "foo", Host: "foo"},
{Original: ":2015", Port: "2015"},
},
[]string{}, []string{"foo"},
[]string{},
[]string{"foo"},
},
{
[]Address{
Expand Down
7 changes: 6 additions & 1 deletion caddyconfig/httpcaddyfile/httptype.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ func (st ServerType) Setup(
if !reflect.DeepEqual(pkiApp, &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}) {
cfg.AppsRaw["pki"] = caddyconfig.JSON(pkiApp, &warnings)
}
if filesystems, ok := options["filesystem"].(caddy.Module); ok {
cfg.AppsRaw["caddy.filesystems"] = caddyconfig.JSON(
filesystems,
&warnings)
}

if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr,
"module",
Expand All @@ -280,7 +286,6 @@ func (st ServerType) Setup(
if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil {
cfg.Admin = adminConfig
}

if pc, ok := options["persist_config"].(string); ok && pc == "off" {
if cfg.Admin == nil {
cfg.Admin = new(caddy.AdminConfig)
Expand Down
5 changes: 0 additions & 5 deletions caddytest/integration/caddyfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
)

func TestRespond(t *testing.T) {

// arrange
tester := caddytest.NewTester(t)
tester.InitServer(`
Expand All @@ -32,7 +31,6 @@ func TestRespond(t *testing.T) {
}

func TestRedirect(t *testing.T) {

// arrange
tester := caddytest.NewTester(t)
tester.InitServer(`
Expand Down Expand Up @@ -61,7 +59,6 @@ func TestRedirect(t *testing.T) {
}

func TestDuplicateHosts(t *testing.T) {

// act and assert
caddytest.AssertLoadError(t,
`
Expand All @@ -76,7 +73,6 @@ func TestDuplicateHosts(t *testing.T) {
}

func TestReadCookie(t *testing.T) {

localhost, _ := url.Parse("http://localhost")
cookie := http.Cookie{
Name: "clientname",
Expand Down Expand Up @@ -110,7 +106,6 @@ func TestReadCookie(t *testing.T) {
}

func TestReplIndex(t *testing.T) {

tester := caddytest.NewTester(t)
tester.InitServer(`
{
Expand Down
1 change: 0 additions & 1 deletion caddytest/integration/reverseproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ func TestSRVReverseProxy(t *testing.T) {
}

func TestDialWithPlaceholderUnix(t *testing.T) {

if runtime.GOOS == "windows" {
t.SkipNow()
}
Expand Down
2 changes: 0 additions & 2 deletions caddytest/integration/sni_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
)

func TestDefaultSNI(t *testing.T) {

// arrange
tester := caddytest.NewTester(t)
tester.InitServer(`{
Expand Down Expand Up @@ -107,7 +106,6 @@ func TestDefaultSNI(t *testing.T) {
}

func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {

// arrange
tester := caddytest.NewTester(t)
tester.InitServer(`
Expand Down
1 change: 0 additions & 1 deletion caddytest/integration/stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {

func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

if r.Host != "127.0.0.1:9443" {
t.Errorf("r.Host doesn't match, %v!", r.Host)
w.WriteHeader(http.StatusNotFound)
Expand Down
12 changes: 12 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (

"github.com/caddyserver/certmagic"
"go.uber.org/zap"

"github.com/caddyserver/caddy/v2/internal/filesystems"
)

// Context is a type which defines the lifetime of modules that
Expand All @@ -37,6 +39,7 @@ import (
// not actually need to do this).
type Context struct {
context.Context

moduleInstances map[string][]Module
cfg *Config
cleanupFuncs []func()
Expand Down Expand Up @@ -81,6 +84,15 @@ func (ctx *Context) OnCancel(f func()) {
ctx.cleanupFuncs = append(ctx.cleanupFuncs, f)
}

// Filesystems returns a ref to the FilesystemMap
func (ctx *Context) Filesystems() FileSystems {
// if no config is loaded, we use a default filesystemmap, which includes the osfs
if ctx.cfg == nil {
return &filesystems.FilesystemMap{}
}
return ctx.cfg.filesystems
}

// LoadModule loads the Caddy module(s) from the specified field of the parent struct
// pointer and returns the loaded module(s). The struct pointer and its field name as
// a string are necessary so that reflection can be used to read the struct tag on the
Expand Down
10 changes: 10 additions & 0 deletions filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package caddy

import "io/fs"

type FileSystems interface {
Register(k string, v fs.FS)
Unregister(k string)
Get(k string) (v fs.FS, ok bool)
Default() fs.FS
}
77 changes: 77 additions & 0 deletions internal/filesystems/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package filesystems

import (
"io/fs"
"strings"
"sync"
)

const (
DefaultFilesystemKey = "default"
)

var DefaultFilesystem = &wrapperFs{key: DefaultFilesystemKey, FS: OsFS{}}

// wrapperFs exists so can easily add to wrapperFs down the line
type wrapperFs struct {
key string
fs.FS
}

// FilesystemMap stores a map of filesystems
// the empty key will be overwritten to be the default key
// it includes a default filesystem, based off the os fs
type FilesystemMap struct {
m sync.Map
}

// note that the first invocation of key cannot be called in a racy context.
func (f *FilesystemMap) key(k string) string {
if k == "" {
k = DefaultFilesystemKey
}
return k
}

// Register will add the filesystem with key to later be retrieved
// A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil
func (f *FilesystemMap) Register(k string, v fs.FS) {
k = f.key(k)
if v == nil {
f.Unregister(k)
return
}
f.m.Store(k, &wrapperFs{key: k, FS: v})
}

// Unregister will remove the filesystem with key from the filesystem map
// if the key is the default key, it will set the default to the osFS instead of deleting it
// modules should call this on cleanup to be safe
func (f *FilesystemMap) Unregister(k string) {
k = f.key(k)
if k == DefaultFilesystemKey {
f.m.Store(k, DefaultFilesystem)
} else {
f.m.Delete(k)
}
}

// Get will get a filesystem with a given key
func (f *FilesystemMap) Get(k string) (v fs.FS, ok bool) {
k = f.key(k)
c, ok := f.m.Load(strings.TrimSpace(k))
if !ok {
if k == DefaultFilesystemKey {
f.m.Store(k, DefaultFilesystem)
return DefaultFilesystem, true
}
return nil, ok
}
return c.(fs.FS), true
}

// Default will get the default filesystem in the filesystem map
func (f *FilesystemMap) Default() fs.FS {
val, _ := f.Get(DefaultFilesystemKey)
return val
}
Loading
Loading