-
Notifications
You must be signed in to change notification settings - Fork 75
/
filesystem.go
178 lines (150 loc) · 4.63 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
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0
package migration
import (
"bytes"
"fmt"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/afero"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
sigsyaml "sigs.k8s.io/yaml"
)
var (
_ Source = &FileSystemSource{}
_ Target = &FileSystemTarget{}
)
// FileSystemSource is a source implementation to read resources from filesystem
type FileSystemSource struct {
index int
items []UnstructuredWithMetadata
afero afero.Afero
}
// FileSystemSourceOption allows you to configure FileSystemSource
type FileSystemSourceOption func(*FileSystemSource)
// FsWithFileSystem configures the filesystem to use. Used mostly for testing.
func FsWithFileSystem(f afero.Fs) FileSystemSourceOption {
return func(fs *FileSystemSource) {
fs.afero = afero.Afero{Fs: f}
}
}
// NewFileSystemSource returns a FileSystemSource
func NewFileSystemSource(dir string, opts ...FileSystemSourceOption) (*FileSystemSource, error) {
fs := &FileSystemSource{
afero: afero.Afero{Fs: afero.NewOsFs()},
}
for _, f := range opts {
f(fs)
}
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return errors.Wrap(err, fmt.Sprintf("cannot read %s", path))
}
if info.IsDir() {
return nil
}
data, err := fs.afero.ReadFile(path)
if err != nil {
return errors.Wrap(err, "cannot read source file")
}
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(string(data)), 1024)
u := &unstructured.Unstructured{}
if err := decoder.Decode(&u); err != nil {
return errors.Wrap(err, "cannot decode read data")
}
fs.items = append(fs.items, UnstructuredWithMetadata{
Object: *u,
Metadata: Metadata{
Path: path,
Category: getCategory(*u),
},
})
return nil
}); err != nil {
return nil, errors.Wrap(err, "cannot read source directory")
}
return fs, nil
}
// HasNext checks the next item
func (fs *FileSystemSource) HasNext() (bool, error) {
return fs.index < len(fs.items), nil
}
// Next returns the next item of slice
func (fs *FileSystemSource) Next() (UnstructuredWithMetadata, error) {
if hasNext, _ := fs.HasNext(); hasNext {
item := fs.items[fs.index]
fs.index++
return item, nil
}
return UnstructuredWithMetadata{}, errors.New("no more elements")
}
// Reset resets the source so that resources can be reread from the beginning.
func (fs *FileSystemSource) Reset() error {
fs.index = 0
return nil
}
// FileSystemTarget is a target implementation to write/patch/delete resources to file system
type FileSystemTarget struct {
afero afero.Afero
parent string
}
// FileSystemTargetOption allows you to configure FileSystemTarget
type FileSystemTargetOption func(*FileSystemTarget)
// FtWithFileSystem configures the filesystem to use. Used mostly for testing.
func FtWithFileSystem(f afero.Fs) FileSystemTargetOption {
return func(ft *FileSystemTarget) {
ft.afero = afero.Afero{Fs: f}
}
}
// WithParentDirectory configures the parent directory for the FileSystemTarget
func WithParentDirectory(parent string) FileSystemTargetOption {
return func(ft *FileSystemTarget) {
ft.parent = parent
}
}
// NewFileSystemTarget returns a FileSystemTarget
func NewFileSystemTarget(opts ...FileSystemTargetOption) *FileSystemTarget {
ft := &FileSystemTarget{
afero: afero.Afero{Fs: afero.NewOsFs()},
}
for _, f := range opts {
f(ft)
}
return ft
}
// Put writes input to filesystem
func (ft *FileSystemTarget) Put(o UnstructuredWithMetadata) error {
b, err := sigsyaml.Marshal(o.Object.Object)
if err != nil {
return errors.Wrap(err, "cannot marshal object")
}
if err := os.MkdirAll(filepath.Join(ft.parent, filepath.Dir(o.Metadata.Path)), 0o750); err != nil {
return errors.Wrapf(err, "cannot mkdirall: %s", filepath.Dir(o.Metadata.Path))
}
if o.Metadata.Parents != "" {
f, err := ft.afero.OpenFile(filepath.Join(ft.parent, o.Metadata.Path), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return errors.Wrap(err, "cannot open file")
}
defer f.Close() //nolint:errcheck
if _, err = fmt.Fprintf(f, "\n---\n\n%s", string(b)); err != nil {
return errors.Wrap(err, "cannot write file")
}
} else {
f, err := ft.afero.Create(filepath.Join(ft.parent, o.Metadata.Path))
if err != nil {
return errors.Wrap(err, "cannot create file")
}
if _, err := f.Write(b); err != nil {
return errors.Wrap(err, "cannot write file")
}
}
return nil
}
// Delete deletes a file from filesystem
func (ft *FileSystemTarget) Delete(o UnstructuredWithMetadata) error {
return ft.afero.Remove(o.Metadata.Path)
}