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  }