github.com/crossplane/upjet@v1.3.0/pkg/migration/filesystem.go (about) 1 // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package migration 6 7 import ( 8 "bytes" 9 "fmt" 10 "os" 11 "path/filepath" 12 13 "github.com/pkg/errors" 14 "github.com/spf13/afero" 15 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 16 "k8s.io/apimachinery/pkg/util/yaml" 17 sigsyaml "sigs.k8s.io/yaml" 18 ) 19 20 var ( 21 _ Source = &FileSystemSource{} 22 _ Target = &FileSystemTarget{} 23 ) 24 25 // FileSystemSource is a source implementation to read resources from filesystem 26 type FileSystemSource struct { 27 index int 28 items []UnstructuredWithMetadata 29 afero afero.Afero 30 } 31 32 // FileSystemSourceOption allows you to configure FileSystemSource 33 type FileSystemSourceOption func(*FileSystemSource) 34 35 // FsWithFileSystem configures the filesystem to use. Used mostly for testing. 36 func FsWithFileSystem(f afero.Fs) FileSystemSourceOption { 37 return func(fs *FileSystemSource) { 38 fs.afero = afero.Afero{Fs: f} 39 } 40 } 41 42 // NewFileSystemSource returns a FileSystemSource 43 func NewFileSystemSource(dir string, opts ...FileSystemSourceOption) (*FileSystemSource, error) { 44 fs := &FileSystemSource{ 45 afero: afero.Afero{Fs: afero.NewOsFs()}, 46 } 47 for _, f := range opts { 48 f(fs) 49 } 50 51 if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 52 if err != nil { 53 return errors.Wrap(err, fmt.Sprintf("cannot read %s", path)) 54 } 55 56 if info.IsDir() { 57 return nil 58 } 59 60 data, err := fs.afero.ReadFile(path) 61 if err != nil { 62 return errors.Wrap(err, "cannot read source file") 63 } 64 65 decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(string(data)), 1024) 66 u := &unstructured.Unstructured{} 67 if err := decoder.Decode(&u); err != nil { 68 return errors.Wrap(err, "cannot decode read data") 69 } 70 71 fs.items = append(fs.items, UnstructuredWithMetadata{ 72 Object: *u, 73 Metadata: Metadata{ 74 Path: path, 75 Category: getCategory(*u), 76 }, 77 }) 78 79 return nil 80 }); err != nil { 81 return nil, errors.Wrap(err, "cannot read source directory") 82 } 83 84 return fs, nil 85 } 86 87 // HasNext checks the next item 88 func (fs *FileSystemSource) HasNext() (bool, error) { 89 return fs.index < len(fs.items), nil 90 } 91 92 // Next returns the next item of slice 93 func (fs *FileSystemSource) Next() (UnstructuredWithMetadata, error) { 94 if hasNext, _ := fs.HasNext(); hasNext { 95 item := fs.items[fs.index] 96 fs.index++ 97 return item, nil 98 } 99 return UnstructuredWithMetadata{}, errors.New("no more elements") 100 } 101 102 // Reset resets the source so that resources can be reread from the beginning. 103 func (fs *FileSystemSource) Reset() error { 104 fs.index = 0 105 return nil 106 } 107 108 // FileSystemTarget is a target implementation to write/patch/delete resources to file system 109 type FileSystemTarget struct { 110 afero afero.Afero 111 parent string 112 } 113 114 // FileSystemTargetOption allows you to configure FileSystemTarget 115 type FileSystemTargetOption func(*FileSystemTarget) 116 117 // FtWithFileSystem configures the filesystem to use. Used mostly for testing. 118 func FtWithFileSystem(f afero.Fs) FileSystemTargetOption { 119 return func(ft *FileSystemTarget) { 120 ft.afero = afero.Afero{Fs: f} 121 } 122 } 123 124 // WithParentDirectory configures the parent directory for the FileSystemTarget 125 func WithParentDirectory(parent string) FileSystemTargetOption { 126 return func(ft *FileSystemTarget) { 127 ft.parent = parent 128 } 129 } 130 131 // NewFileSystemTarget returns a FileSystemTarget 132 func NewFileSystemTarget(opts ...FileSystemTargetOption) *FileSystemTarget { 133 ft := &FileSystemTarget{ 134 afero: afero.Afero{Fs: afero.NewOsFs()}, 135 } 136 for _, f := range opts { 137 f(ft) 138 } 139 return ft 140 } 141 142 // Put writes input to filesystem 143 func (ft *FileSystemTarget) Put(o UnstructuredWithMetadata) error { 144 b, err := sigsyaml.Marshal(o.Object.Object) 145 if err != nil { 146 return errors.Wrap(err, "cannot marshal object") 147 } 148 if err := os.MkdirAll(filepath.Join(ft.parent, filepath.Dir(o.Metadata.Path)), 0o750); err != nil { 149 return errors.Wrapf(err, "cannot mkdirall: %s", filepath.Dir(o.Metadata.Path)) 150 } 151 if o.Metadata.Parents != "" { 152 f, err := ft.afero.OpenFile(filepath.Join(ft.parent, o.Metadata.Path), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) 153 if err != nil { 154 return errors.Wrap(err, "cannot open file") 155 } 156 157 defer f.Close() //nolint:errcheck 158 159 if _, err = fmt.Fprintf(f, "\n---\n\n%s", string(b)); err != nil { 160 return errors.Wrap(err, "cannot write file") 161 } 162 } else { 163 f, err := ft.afero.Create(filepath.Join(ft.parent, o.Metadata.Path)) 164 if err != nil { 165 return errors.Wrap(err, "cannot create file") 166 } 167 if _, err := f.Write(b); err != nil { 168 return errors.Wrap(err, "cannot write file") 169 } 170 } 171 172 return nil 173 } 174 175 // Delete deletes a file from filesystem 176 func (ft *FileSystemTarget) Delete(o UnstructuredWithMetadata) error { 177 return ft.afero.Remove(o.Metadata.Path) 178 }