github.com/replicatedhq/ship@v0.55.0/pkg/filetree/loader.go (about) 1 package filetree 2 3 import ( 4 "os" 5 "path" 6 "strings" 7 8 "github.com/go-kit/kit/log" 9 "github.com/go-kit/kit/log/level" 10 "github.com/pkg/errors" 11 "github.com/replicatedhq/ship/pkg/state" 12 "github.com/spf13/afero" 13 "sigs.k8s.io/kustomize/k8sdeps/kunstruct" 14 "sigs.k8s.io/kustomize/pkg/resource" 15 ) 16 17 const ( 18 CustomResourceDefinition = "CustomResourceDefinition" 19 PatchesFolder = "overlays" 20 ResourcesFolder = "resources" 21 ) 22 23 // A Loader returns a struct representation 24 // of a filesystem directory tree 25 type Loader interface { 26 LoadTree(root string) (*Node, error) 27 // someday this should return an overlay too 28 LoadFile(root string, path string) ([]byte, error) 29 } 30 31 // NewLoader builds an aferoLoader, used with dig 32 func NewLoader( 33 fs afero.Afero, 34 logger log.Logger, 35 stateManager state.Manager, 36 ) Loader { 37 return &aferoLoader{ 38 FS: fs, 39 Logger: logger, 40 StateManager: stateManager, 41 } 42 } 43 44 type aferoLoader struct { 45 Logger log.Logger 46 FS afero.Afero 47 StateManager state.Manager 48 excludedBases map[string]string 49 patches map[string]string 50 resources map[string]string 51 } 52 53 func (a *aferoLoader) loadShipOverlay() error { 54 currentState, err := a.StateManager.CachedState() 55 if err != nil { 56 return errors.Wrap(err, "failed to load state") 57 } 58 59 kustomize := currentState.CurrentKustomize() 60 if kustomize == nil { 61 kustomize = &state.Kustomize{} 62 } 63 64 shipOverlay := kustomize.Ship() 65 baseMap := make(map[string]string) 66 for _, base := range shipOverlay.ExcludedBases { 67 baseMap[base] = base 68 } 69 a.excludedBases = baseMap 70 a.patches = shipOverlay.Patches 71 a.resources = shipOverlay.Resources 72 return nil 73 } 74 75 func (a *aferoLoader) LoadTree(root string) (*Node, error) { 76 if err := a.loadShipOverlay(); err != nil { 77 return nil, errors.Wrapf(err, "load overlays") 78 } 79 80 fs := afero.Afero{Fs: afero.NewBasePathFs(a.FS, root)} 81 82 files, err := fs.ReadDir("/") 83 if err != nil { 84 return nil, errors.Wrapf(err, "read dir %q", root) 85 } 86 87 rootNode := Node{ 88 Path: "/", 89 Name: "/", 90 Children: []Node{}, 91 } 92 patchesRootNode := Node{ 93 Path: "/", 94 Name: PatchesFolder, 95 Children: []Node{}, 96 } 97 resourceRootNode := Node{ 98 Path: "/", 99 Name: ResourcesFolder, 100 Children: []Node{}, 101 } 102 103 populatedBase, err := a.loadTree(fs, rootNode, files) 104 if err != nil { 105 return nil, errors.Wrap(err, "load tree") 106 } 107 108 populatedPatches := a.loadOverlayTree(patchesRootNode, a.patches) 109 populatedResources := a.loadOverlayTree(resourceRootNode, a.resources) 110 111 children := []Node{populatedBase} 112 113 if len(populatedPatches.Children) != 0 { 114 children = append(children, populatedPatches) 115 } 116 117 if len(populatedResources.Children) != 0 { 118 children = append(children, populatedResources) 119 } 120 121 return &Node{ 122 Path: "/", 123 Name: "/", 124 Children: children, 125 }, nil 126 } 127 128 // todo move this to a new struct or something 129 func (a *aferoLoader) LoadFile(root string, file string) ([]byte, error) { 130 fs := afero.Afero{Fs: afero.NewBasePathFs(a.FS, root)} 131 contents, err := fs.ReadFile(file) 132 if err != nil { 133 return []byte{}, errors.Wrap(err, "read file") 134 } 135 136 return contents, nil 137 } 138 139 func (a *aferoLoader) loadTree(fs afero.Afero, current Node, files []os.FileInfo) (Node, error) { 140 if len(files) == 0 { 141 return current, nil 142 } 143 144 file, rest := files[0], files[1:] 145 filePath := path.Join(current.Path, file.Name()) 146 147 // no thanks 148 if isSymlink(file) { 149 level.Debug(a.Logger).Log("event", "symlink.skip", "file", filePath) 150 return a.loadTree(fs, current, rest) 151 } 152 153 if !file.IsDir() { 154 _, hasOverlay := a.patches[filePath] 155 156 fileB, err := fs.ReadFile(filePath) 157 if err != nil { 158 return current, errors.Wrapf(err, "read file %s", file.Name()) 159 } 160 161 _, exists := a.excludedBases[filePath] 162 return a.loadTree(fs, current.withChild(Node{ 163 Name: file.Name(), 164 Path: filePath, 165 HasOverlay: hasOverlay, 166 IsSupported: IsSupported(fileB), 167 IsExcluded: exists, 168 }), rest) 169 } 170 171 subFiles, err := fs.ReadDir(filePath) 172 if err != nil { 173 return current, errors.Wrapf(err, "read dir %q", file.Name()) 174 } 175 176 subTree := Node{ 177 Name: file.Name(), 178 Path: filePath, 179 Children: []Node{}, 180 } 181 182 subTreeLoaded, err := a.loadTree(fs, subTree, subFiles) 183 if err != nil { 184 return current, errors.Wrapf(err, "load tree %q", file.Name()) 185 } 186 187 return a.loadTree(fs, current.withChild(subTreeLoaded), rest) 188 } 189 190 func isSymlink(file os.FileInfo) bool { 191 return file.Mode()&os.ModeSymlink != 0 192 } 193 194 func IsSupported(file []byte) bool { 195 resourceFactory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) 196 197 resources, err := resourceFactory.SliceFromBytes(file) 198 if err != nil { 199 return false 200 } 201 if len(resources) != 1 { 202 return false 203 } 204 r := resources[0] 205 206 // any kind but CRDs are supported 207 return r.GetKind() != CustomResourceDefinition 208 } 209 210 func (n Node) withChild(child Node) Node { 211 return Node{ 212 Name: n.Name, 213 Path: n.Path, 214 Children: append(n.Children, child), 215 IsSupported: n.IsSupported, 216 HasOverlay: n.HasOverlay, 217 } 218 } 219 220 func (a *aferoLoader) loadOverlayTree(kustomizationNode Node, files map[string]string) Node { 221 filledTree := &kustomizationNode 222 for patchPath := range files { 223 splitPatchPath := strings.Split(patchPath, "/")[1:] 224 filledTree = a.createOverlayNode(filledTree, splitPatchPath) 225 } 226 return *filledTree 227 } 228 229 func (a *aferoLoader) createOverlayNode(kustomizationNode *Node, pathToOverlay []string) *Node { 230 if len(pathToOverlay) == 0 { 231 return kustomizationNode 232 } 233 234 pathToMatch, restOfPath := pathToOverlay[0], pathToOverlay[1:] 235 filePath := path.Join(kustomizationNode.Path, pathToMatch) 236 237 for i := range kustomizationNode.Children { 238 if kustomizationNode.Children[i].Path == pathToMatch || kustomizationNode.Children[i].Name == pathToMatch { 239 a.createOverlayNode(&kustomizationNode.Children[i], restOfPath) 240 return kustomizationNode 241 } 242 } 243 244 nextNode := Node{ 245 Name: pathToMatch, 246 Path: filePath, 247 } 248 loadedChild := a.createOverlayNode(&nextNode, restOfPath) 249 kustomizationNode.Children = append(kustomizationNode.Children, *loadedChild) 250 return kustomizationNode 251 }