Skip to content

Commit

Permalink
filesystem: Globally declared filesystems, fs directive (#5833)
Browse files Browse the repository at this point in the history
  • Loading branch information
elee1766 committed Jan 13, 2024
1 parent b359ca5 commit c839a98
Show file tree
Hide file tree
Showing 30 changed files with 450 additions and 219 deletions.
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
}

0 comments on commit c839a98

Please sign in to comment.