github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/util/diff/pkgdiff.go (about) 1 // Copyright 2020 Google LLC 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 diff 16 17 import ( 18 "bytes" 19 "os" 20 "path/filepath" 21 22 "github.com/GoogleContainerTools/kpt/internal/pkg" 23 "github.com/GoogleContainerTools/kpt/internal/util/attribution" 24 "github.com/GoogleContainerTools/kpt/internal/util/pkgutil" 25 kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" 26 "sigs.k8s.io/kustomize/kyaml/filesys" 27 "sigs.k8s.io/kustomize/kyaml/kio" 28 "sigs.k8s.io/kustomize/kyaml/sets" 29 "sigs.k8s.io/kustomize/kyaml/yaml" 30 ) 31 32 func PkgDiff(pkg1, pkg2 string) (sets.String, error) { 33 pkg1Files, err := pkgSet(pkg1) 34 if err != nil { 35 return sets.String{}, err 36 } 37 38 pkg2Files, err := pkgSet(pkg2) 39 if err != nil { 40 return sets.String{}, err 41 } 42 43 diff := pkg1Files.SymmetricDifference(pkg2Files) 44 45 for _, f := range pkg1Files.Intersection(pkg2Files).List() { 46 fi, err := os.Stat(filepath.Join(pkg1, f)) 47 if err != nil { 48 return diff, err 49 } 50 51 if fi.IsDir() { 52 continue 53 } 54 55 fileName := filepath.Base(f) 56 if fileName == kptfilev1.KptFileName { 57 equal, err := kptfilesEqual(pkg1, pkg2, f) 58 if err != nil { 59 return diff, err 60 } 61 if !equal { 62 diff.Insert(f) 63 } 64 } else { 65 b1, err := os.ReadFile(filepath.Join(pkg1, f)) 66 if err != nil { 67 return diff, err 68 } 69 b2, err := os.ReadFile(filepath.Join(pkg2, f)) 70 if err != nil { 71 return diff, err 72 } 73 if !nonKptfileEquals(string(b1), string(b2)) { 74 diff.Insert(f) 75 } 76 } 77 } 78 return diff, nil 79 } 80 81 func kptfilesEqual(pkg1, pkg2, filePath string) (bool, error) { 82 pkg1Kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, filepath.Join(pkg1, filepath.Dir(filePath))) 83 if err != nil { 84 return false, err 85 } 86 pkg2Kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, filepath.Join(pkg2, filepath.Dir(filePath))) 87 if err != nil { 88 return false, err 89 } 90 91 // Diffs in Upstream and UpstreamLock should be ignored. 92 pkg1Kf.Upstream = &kptfilev1.Upstream{} 93 pkg1Kf.UpstreamLock = &kptfilev1.UpstreamLock{} 94 pkg2Kf.Upstream = &kptfilev1.Upstream{} 95 pkg2Kf.UpstreamLock = &kptfilev1.UpstreamLock{} 96 97 pkg1Bytes, err := yaml.Marshal(pkg1Kf) 98 if err != nil { 99 return false, err 100 } 101 pkg2Bytes, err := yaml.Marshal(pkg2Kf) 102 if err != nil { 103 return false, err 104 } 105 return bytes.Equal(pkg1Bytes, pkg2Bytes), nil 106 } 107 108 func pkgSet(pkgPath string) (sets.String, error) { 109 pkgFiles := sets.String{} 110 if err := pkgutil.WalkPackage(pkgPath, func(path string, info os.FileInfo, err error) error { 111 if err != nil { 112 return err 113 } 114 relPath, err := filepath.Rel(pkgPath, path) 115 if err != nil { 116 return err 117 } 118 pkgFiles.Insert(relPath) 119 return nil 120 }); err != nil { 121 return sets.String{}, err 122 } 123 return pkgFiles, nil 124 } 125 126 // nonKptfileEquals returns true if contents of two non-Kptfiles are equal 127 // since the changes to addmetricsannotation.CNRMMetricsAnnotation is made 128 // by kpt, we should not treat it as changes made by user, so delete the annotation 129 // before comparing 130 func nonKptfileEquals(s1, s2 string) bool { 131 out1 := &bytes.Buffer{} 132 out2 := &bytes.Buffer{} 133 err := kio.Pipeline{ 134 Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(s1)}}, 135 Filters: []kio.Filter{kio.FilterAll(yaml.AnnotationClearer{Key: attribution.CNRMMetricsAnnotation})}, 136 Outputs: []kio.Writer{kio.ByteWriter{Writer: out1}}, 137 }.Execute() 138 if err != nil { 139 return bytes.Equal([]byte(s1), []byte(s2)) 140 } 141 err = kio.Pipeline{ 142 Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(s2)}}, 143 Filters: []kio.Filter{kio.FilterAll(yaml.AnnotationClearer{Key: attribution.CNRMMetricsAnnotation})}, 144 Outputs: []kio.Writer{kio.ByteWriter{Writer: out2}}, 145 }.Execute() 146 if err != nil { 147 return bytes.Equal([]byte(s1), []byte(s2)) 148 } 149 return out1.String() == out2.String() 150 }