github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/internal/util/pkgutil/pkgutil.go (about) 1 // Copyright 2020 The kpt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pkgutil 16 17 import ( 18 "io" 19 "os" 20 "path/filepath" 21 "sort" 22 "strings" 23 24 "github.com/GoogleContainerTools/kpt/internal/pkg" 25 kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" 26 "github.com/GoogleContainerTools/kpt/pkg/kptfile/kptfileutil" 27 "sigs.k8s.io/kustomize/kyaml/copyutil" 28 "sigs.k8s.io/kustomize/kyaml/filesys" 29 "sigs.k8s.io/kustomize/kyaml/kio" 30 "sigs.k8s.io/kustomize/kyaml/kio/filters" 31 ) 32 33 // WalkPackage walks the package defined at src and provides a callback for 34 // every folder and file. Any subpackages and the .git folder are excluded. 35 func WalkPackage(src string, c func(string, os.FileInfo, error) error) error { 36 excludedDirs := make(map[string]bool) 37 return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 38 if err != nil { 39 return c(path, info, err) 40 } 41 // don't copy the .git dir 42 if path != src { 43 rel := strings.TrimPrefix(path, src) 44 if copyutil.IsDotGitFolder(rel) { 45 return nil 46 } 47 } 48 49 for dir := range excludedDirs { 50 if strings.HasPrefix(path, dir) { 51 return nil 52 } 53 } 54 55 if info.IsDir() { 56 _, err := os.Stat(filepath.Join(path, kptfilev1.KptFileName)) 57 if err != nil && !os.IsNotExist(err) { 58 return c(path, info, err) 59 } 60 if err == nil && path != src { 61 excludedDirs[path] = true 62 return nil 63 } 64 } 65 return c(path, info, err) 66 }) 67 } 68 69 // CopyPackage copies the content of a single package from src to dst. If includeSubpackages 70 // is true, it will copy resources belonging to any subpackages. 71 func CopyPackage(src, dst string, copyRootKptfile bool, matcher pkg.SubpackageMatcher) error { 72 subpackagesToCopy, err := pkg.Subpackages(filesys.FileSystemOrOnDisk{}, src, matcher, true) 73 if err != nil { 74 return err 75 } 76 77 err = WalkPackage(src, func(path string, info os.FileInfo, err error) error { 78 if err != nil { 79 return err 80 } 81 82 // path is an absolute path, rather than a path relative to src. 83 // e.g. if src is /path/to/package, then path might be /path/to/package/and/sub/dir 84 // we need the path relative to src `and/sub/dir` when we are copying the files to dest. 85 copyTo := strings.TrimPrefix(path, src) 86 if copyTo == "/Kptfile" { 87 _, err := os.Stat(filepath.Join(dst, copyTo)) 88 if err == nil { 89 return nil 90 } 91 if !os.IsNotExist(err) { 92 return err 93 } 94 } 95 96 // make directories that don't exist 97 if info.IsDir() { 98 return os.MkdirAll(filepath.Join(dst, copyTo), info.Mode()) 99 } 100 101 if path == filepath.Join(src, kptfilev1.KptFileName) && !copyRootKptfile { 102 return nil 103 } 104 105 // copy file by reading and writing it 106 b, err := os.ReadFile(filepath.Join(src, copyTo)) 107 if err != nil { 108 return err 109 } 110 err = os.WriteFile(filepath.Join(dst, copyTo), b, info.Mode()) 111 if err != nil { 112 return err 113 } 114 115 return nil 116 }) 117 118 if err != nil { 119 return err 120 } 121 122 for _, subpackage := range subpackagesToCopy { 123 subpackageSrc := filepath.Join(src, subpackage) 124 // subpackageDest := filepath.Join(dst, strings.TrimPrefix(subpackage, src)) 125 err = filepath.Walk(subpackageSrc, func(path string, info os.FileInfo, err error) error { 126 if err != nil { 127 return err 128 } 129 // don't copy the .git dir 130 if path != src { 131 rel := strings.TrimPrefix(path, subpackageSrc) 132 if copyutil.IsDotGitFolder(rel) { 133 return nil 134 } 135 } 136 137 copyTo := strings.TrimPrefix(path, src) 138 if info.IsDir() { 139 return os.MkdirAll(filepath.Join(dst, copyTo), info.Mode()) 140 } 141 142 if copyTo == "/Kptfile" { 143 _, err := os.Stat(filepath.Join(dst, copyTo)) 144 if err == nil { 145 return nil 146 } 147 if !os.IsNotExist(err) { 148 return err 149 } 150 } 151 152 // copy file by reading and writing it 153 b, err := os.ReadFile(filepath.Join(src, copyTo)) 154 if err != nil { 155 return err 156 } 157 err = os.WriteFile(filepath.Join(dst, copyTo), b, info.Mode()) 158 if err != nil { 159 return err 160 } 161 162 return nil 163 }) 164 if err != nil { 165 return err 166 } 167 } 168 169 return nil 170 } 171 172 func RemovePackageContent(path string, removeRootKptfile bool) error { 173 // Walk the package (while ignoring subpackages) and delete all files. 174 // We capture the paths to any subdirectories in the package so we 175 // can handle those later. We can't do it while walking the package 176 // since we don't want to end up deleting directories that might 177 // contain a nested subpackage. 178 var dirs []string 179 if err := WalkPackage(path, func(p string, info os.FileInfo, err error) error { 180 if err != nil { 181 if os.IsNotExist(err) { 182 return nil 183 } 184 return err 185 } 186 187 if info.IsDir() { 188 if p != path { 189 dirs = append(dirs, p) 190 } 191 return nil 192 } 193 194 if p == filepath.Join(path, kptfilev1.KptFileName) && !removeRootKptfile { 195 return nil 196 } 197 198 return os.Remove(p) 199 }); err != nil { 200 return err 201 } 202 203 // Delete any of the directories in the package that are 204 // empty. We start with the most deeply nested directories 205 // so we can just check every directory for files/directories. 206 sort.Slice(dirs, SubPkgFirstSorter(dirs)) 207 for _, p := range dirs { 208 f, err := os.Open(p) 209 if err != nil { 210 return err 211 } 212 // List up to one file or folder in the directory. 213 _, err = f.Readdirnames(1) 214 if err != nil && err != io.EOF { 215 return err 216 } 217 // If the returned error is EOF, it means the folder 218 // was empty and we can remove it. 219 if err == io.EOF { 220 err = os.RemoveAll(p) 221 if err != nil { 222 return err 223 } 224 } 225 } 226 return nil 227 } 228 229 // RootPkgFirstSorter returns a "less" function that can be used with the 230 // sort.Slice function to correctly sort package paths so parent packages 231 // are always before subpackages. 232 func RootPkgFirstSorter(paths []string) func(i, j int) bool { 233 return func(i, j int) bool { 234 iPath := paths[i] 235 jPath := paths[j] 236 if iPath == "." { 237 return true 238 } 239 if jPath == "." { 240 return false 241 } 242 // First sort based on the number of segments. 243 iSegmentCount := len(filepath.SplitList(iPath)) 244 jSegmentCount := len(filepath.SplitList(jPath)) 245 if jSegmentCount != iSegmentCount { 246 return iSegmentCount < jSegmentCount 247 } 248 // If two paths are at the same depth, just sort lexicographically. 249 return iPath < jPath 250 } 251 } 252 253 // SubPkgFirstSorter returns a "less" function that can be used with the 254 // sort.Slice function to correctly sort package paths so subpackages are 255 // always before parent packages. 256 func SubPkgFirstSorter(paths []string) func(i, j int) bool { 257 sorter := RootPkgFirstSorter(paths) 258 return func(i, j int) bool { 259 return !sorter(i, j) 260 } 261 } 262 263 // FindSubpackagesForPaths traverses the provided package paths 264 // and finds all subpackages using the provided pkgLocatorFunc 265 func FindSubpackagesForPaths(matcher pkg.SubpackageMatcher, recurse bool, pkgPaths ...string) ([]string, error) { 266 uniquePaths := make(map[string]bool) 267 for _, path := range pkgPaths { 268 paths, err := pkg.Subpackages(filesys.FileSystemOrOnDisk{}, path, matcher, recurse) 269 if err != nil { 270 return []string{}, err 271 } 272 for _, p := range paths { 273 uniquePaths[p] = true 274 } 275 } 276 paths := []string{} 277 for p := range uniquePaths { 278 paths = append(paths, p) 279 } 280 sort.Slice(paths, RootPkgFirstSorter(paths)) 281 return paths, nil 282 } 283 284 // FormatPackage formats resources and meta-resources in the package and all its subpackages 285 func FormatPackage(pkgPath string) { 286 inout := &kio.LocalPackageReadWriter{ 287 PackagePath: pkgPath, 288 MatchFilesGlob: append(kio.DefaultMatch, kptfilev1.KptFileName), 289 PreserveSeqIndent: true, 290 WrapBareSeqNode: true, 291 } 292 f := &filters.FormatFilter{ 293 UseSchema: true, 294 } 295 err := kio.Pipeline{ 296 Inputs: []kio.Reader{inout}, 297 Filters: []kio.Filter{f}, 298 Outputs: []kio.Writer{inout}, 299 }.Execute() 300 if err != nil { 301 // do not throw error if formatting fails 302 return 303 } 304 err = RoundTripKptfilesInPkg(pkgPath) 305 if err != nil { 306 // do not throw error if formatting fails 307 return 308 } 309 } 310 311 // RoundTripKptfilesInPkg reads and writes all Kptfiles in the package including 312 // subpackages. This is used to format Kptfiles in the order of go structures 313 // TODO: phanimarupaka remove this method after addressing https://github.com/GoogleContainerTools/kpt/issues/2052 314 func RoundTripKptfilesInPkg(pkgPath string) error { 315 paths, err := pkg.Subpackages(filesys.FileSystemOrOnDisk{}, pkgPath, pkg.All, true) 316 if err != nil { 317 return err 318 } 319 320 var pkgsPaths []string 321 for _, path := range paths { 322 // join pkgPath as the paths are relative to pkgPath 323 pkgsPaths = append(pkgsPaths, filepath.Join(pkgPath, path)) 324 } 325 // include root package as well 326 pkgsPaths = append(pkgsPaths, pkgPath) 327 328 for _, pkgPath := range pkgsPaths { 329 kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, pkgPath) 330 if err != nil { 331 // do not throw error if formatting fails 332 return err 333 } 334 err = kptfileutil.WriteFile(pkgPath, kf) 335 if err != nil { 336 // do not throw error if formatting fails 337 return err 338 } 339 } 340 return nil 341 } 342 343 // Exists returns true if a file or directory exists on the provided path, 344 // and false otherwise. 345 func Exists(path string) (bool, error) { 346 _, err := os.Stat(path) 347 if err != nil && !os.IsNotExist(err) { 348 return false, err 349 } 350 return !os.IsNotExist(err), nil 351 }