1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
package datafs
import (
"context"
"fmt"
"io/fs"
"net/url"
"strings"
"github.com/hairyhenderson/go-fsimpl"
"github.com/hairyhenderson/go-fsimpl/vaultfs/vaultauth"
"github.com/hairyhenderson/gomplate/v4/internal/urlhelpers"
)
type fsProviderCtxKey struct{}
// ContextWithFSProvider returns a context with the given FSProvider. Should
// only be used in tests.
func ContextWithFSProvider(ctx context.Context, fsp fsimpl.FSProvider) context.Context {
return context.WithValue(ctx, fsProviderCtxKey{}, fsp)
}
// FSProviderFromContext returns the FSProvider from the context, if any
func FSProviderFromContext(ctx context.Context) fsimpl.FSProvider {
if fsp, ok := ctx.Value(fsProviderCtxKey{}).(fsimpl.FSProvider); ok {
return fsp
}
return nil
}
// FSysForPath returns an [io/fs.FS] for the given path (which may be an URL),
// rooted at /. A [fsimpl.FSProvider] is required to be present in ctx,
// otherwise an error is returned.
func FSysForPath(ctx context.Context, path string) (fs.FS, error) {
u, err := urlhelpers.ParseSourceURL(path)
if err != nil {
return nil, err
}
fsp := FSProviderFromContext(ctx)
if fsp == nil {
return nil, fmt.Errorf("no filesystem provider in context")
}
origPath := u.Path
switch u.Scheme {
case "git+file", "git+http", "git+https", "git+ssh", "git":
// git URLs are special - they have double-slashes that separate a repo from
// a path in the repo. A missing double-slash means the path is the root.
u.Path, _, _ = strings.Cut(u.Path, "//")
}
switch u.Scheme {
case "git+http", "git+https", "git+ssh", "git":
// no-op, these are handled
case "aws+sm":
// An aws+sm URL can be opaque, best not disturb it
case "", "file", "git+file":
// default to "/" so we have a rooted filesystem for all schemes, but also
// support volumes on Windows
root, name, rerr := ResolveLocalPath(nil, u.Path)
if rerr != nil {
return nil, fmt.Errorf("resolve local path %q: %w", origPath, rerr)
}
// windows absolute paths need a slash between the volume and path
if root != "" && root[0] != '/' {
u.Path = root + "/" + name
} else {
u.Path = root + name
}
// if this is a drive letter, add a trailing slash
if len(u.Path) == 2 && u.Path[0] != '/' && u.Path[1] == ':' {
u.Path += "/"
} else if u.Path[0] != '/' {
u.Path += "/"
}
// if this starts with a drive letter, add a leading slash
// NOPE - this breaks lots of things
// if len(u.Path) > 2 && u.Path[0] != '/' && u.Path[1] == ':' {
// u.Path = "/" + u.Path
// }
default:
u.Path = "/"
}
fsys, err := fsp.New(u)
if err != nil {
return nil, fmt.Errorf("filesystem provider for %q unavailable: %w", path, err)
}
// inject vault auth methods if needed
switch u.Scheme {
case "vault", "vault+http", "vault+https":
fileFsys, err := fsp.New(&url.URL{Scheme: "file", Path: "/"})
if err != nil {
return nil, fmt.Errorf("filesystem provider for %q unavailable: %w", path, err)
}
fsys = vaultauth.WithAuthMethod(compositeVaultAuthMethod(fileFsys), fsys)
}
fsys = fsimpl.WithContextFS(ctx, fsys)
return fsys, nil
}
type fsp struct {
newFunc func(*url.URL) (fs.FS, error)
schemes []string
}
func (p fsp) Schemes() []string {
return p.schemes
}
func (p fsp) New(u *url.URL) (fs.FS, error) {
return p.newFunc(u)
}
// WrappedFSProvider is an FSProvider that returns the given fs.FS
func WrappedFSProvider(fsys fs.FS, schemes ...string) fsimpl.FSProvider {
return fsp{
newFunc: func(_ *url.URL) (fs.FS, error) { return fsys, nil },
schemes: schemes,
}
}
|