-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
filesystem.go
181 lines (146 loc) · 4.11 KB
/
filesystem.go
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package filesystem
import (
"io"
"os"
"path/filepath"
"github.com/spf13/afero"
)
const (
createOrUpdate = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
defaultDirectoryPermission os.FileMode = 0755
defaultFilePermission os.FileMode = 0644
)
// FileSystem is an IO wrapper to create files
type FileSystem interface {
// Exists checks if the file exists
Exists(path string) (bool, error)
// Open opens the file and returns a self-closing io.Reader.
Open(path string) (io.ReadCloser, error)
// Create creates the directory and file and returns a self-closing
// io.Writer pointing to that file. If the file exists, it truncates it.
Create(path string) (io.Writer, error)
}
// fileSystem implements FileSystem
type fileSystem struct {
fs afero.Fs
dirPerm os.FileMode
filePerm os.FileMode
fileMode int
}
// New returns a new FileSystem
func New(options ...Options) FileSystem {
// Default values
fs := fileSystem{
fs: afero.NewOsFs(),
dirPerm: defaultDirectoryPermission,
filePerm: defaultFilePermission,
fileMode: createOrUpdate,
}
// Apply options
for _, option := range options {
option(&fs)
}
return fs
}
// Options configure FileSystem
type Options func(system *fileSystem)
// DirectoryPermissions makes FileSystem.Create use the provided directory
// permissions
func DirectoryPermissions(dirPerm os.FileMode) Options {
return func(fs *fileSystem) {
fs.dirPerm = dirPerm
}
}
// FilePermissions makes FileSystem.Create use the provided file permissions
func FilePermissions(filePerm os.FileMode) Options {
return func(fs *fileSystem) {
fs.filePerm = filePerm
}
}
// Exists implements FileSystem.Exists
func (fs fileSystem) Exists(path string) (bool, error) {
exists, err := afero.Exists(fs.fs, path)
if err != nil {
return exists, fileExistsError{path, err}
}
return exists, nil
}
// Open implements FileSystem.Open
func (fs fileSystem) Open(path string) (io.ReadCloser, error) {
rc, err := fs.fs.Open(path)
if err != nil {
return nil, openFileError{path, err}
}
return &readFile{path, rc}, nil
}
// Create implements FileSystem.Create
func (fs fileSystem) Create(path string) (io.Writer, error) {
// Create the directory if needed
if err := fs.fs.MkdirAll(filepath.Dir(path), fs.dirPerm); err != nil {
return nil, createDirectoryError{path, err}
}
// Create or truncate the file
wc, err := fs.fs.OpenFile(path, fs.fileMode, fs.filePerm)
if err != nil {
return nil, createFileError{path, err}
}
return &writeFile{path, wc}, nil
}
var _ io.ReadCloser = &readFile{}
// readFile implements io.Reader
type readFile struct {
path string
io.ReadCloser
}
// Read implements io.Reader.ReadCloser
func (f *readFile) Read(content []byte) (n int, err error) {
// Read the content
n, err = f.ReadCloser.Read(content)
// EOF is a special case error that we can't wrap
if err == io.EOF {
return
}
if err != nil {
return n, readFileError{f.path, err}
}
return n, nil
}
// Close implements io.Reader.ReadCloser
func (f *readFile) Close() error {
if err := f.ReadCloser.Close(); err != nil {
return closeFileError{f.path, err}
}
return nil
}
// writeFile implements io.Writer
type writeFile struct {
path string
io.WriteCloser
}
// Write implements io.Writer.Write
func (f *writeFile) Write(content []byte) (n int, err error) {
// Close the file when we end writing
defer func() {
if closeErr := f.Close(); err == nil && closeErr != nil {
err = closeFileError{f.path, err}
}
}()
// Write the content
n, err = f.WriteCloser.Write(content)
if err != nil {
return n, writeFileError{f.path, err}
}
return n, nil
}