github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/util/update/fastforward.go (about)

     1  // Copyright 2019 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 update
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  
    22  	"github.com/GoogleContainerTools/kpt/internal/errors"
    23  	"github.com/GoogleContainerTools/kpt/internal/pkg"
    24  	"github.com/GoogleContainerTools/kpt/internal/types"
    25  	pkgdiff "github.com/GoogleContainerTools/kpt/internal/util/diff"
    26  	"github.com/GoogleContainerTools/kpt/internal/util/pkgutil"
    27  	kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
    28  	"github.com/GoogleContainerTools/kpt/pkg/kptfile/kptfileutil"
    29  	"sigs.k8s.io/kustomize/kyaml/filesys"
    30  	"sigs.k8s.io/kustomize/kyaml/sets"
    31  )
    32  
    33  // Updater updates a package to a new upstream version.
    34  //
    35  // If the package at pkgPath differs from the upstream ref it was fetch from, then Update will
    36  // fail without making any changes.
    37  type FastForwardUpdater struct{}
    38  
    39  var kptfileSet = func() sets.String {
    40  	s := sets.String{}
    41  	s.Insert(kptfilev1.KptFileName)
    42  	return s
    43  }()
    44  
    45  // We should try to pull the common code up into the Update command.
    46  func (u FastForwardUpdater) Update(options Options) error {
    47  	const op errors.Op = "update.Update"
    48  	// Verify that there are no local changes that would prevent us from
    49  	// using the FastForward strategy.
    50  	if err := u.checkForLocalChanges(options.LocalPath, options.OriginPath); err != nil {
    51  		return errors.E(op, types.UniquePath(options.LocalPath), err)
    52  	}
    53  
    54  	if err := (&ReplaceUpdater{}).Update(options); err != nil {
    55  		return errors.E(op, types.UniquePath(options.LocalPath), err)
    56  	}
    57  	return nil
    58  }
    59  
    60  func (u FastForwardUpdater) checkForLocalChanges(localPath, originalPath string) error {
    61  	const op errors.Op = "update.checkForLocalChanges"
    62  	found, err := pkgutil.Exists(originalPath)
    63  	if err != nil {
    64  		return errors.E(op, types.UniquePath(localPath), err)
    65  	}
    66  	if !found {
    67  		return nil
    68  	}
    69  
    70  	subPkgPaths, err := pkgutil.FindSubpackagesForPaths(pkg.Local, true, localPath, originalPath)
    71  	if err != nil {
    72  		return errors.E(op, types.UniquePath(localPath), err)
    73  	}
    74  	aggDiff := sets.String{}
    75  	for _, subPkgPath := range append([]string{"."}, subPkgPaths...) {
    76  		localSubPkgPath := filepath.Join(localPath, subPkgPath)
    77  		originalSubPkgPath := filepath.Join(originalPath, subPkgPath)
    78  
    79  		localExists, err := pkgutil.Exists(localSubPkgPath)
    80  		if err != nil {
    81  			return errors.E(op, types.UniquePath(localSubPkgPath), err)
    82  		}
    83  		originalExists, err := pkgutil.Exists(originalSubPkgPath)
    84  		if err != nil {
    85  			return errors.E(op, types.UniquePath(localSubPkgPath), err)
    86  		}
    87  		if !originalExists || !localExists {
    88  			aggDiff.Insert("%s (Package)", subPkgPath)
    89  			continue
    90  		}
    91  		d, err := pkgdiff.PkgDiff(localSubPkgPath, originalSubPkgPath)
    92  		if err != nil {
    93  			return errors.E(op, types.UniquePath(localSubPkgPath), err)
    94  		}
    95  		// If the original package didn't have a Kptfile, one was created
    96  		// in local, but we don't consider that a change unless the user
    97  		// has made additional changes.
    98  		if d.Has(kptfilev1.KptFileName) && subPkgPath == "." {
    99  			hasDiff, err := hasKfDiff(localSubPkgPath, originalSubPkgPath)
   100  			if err != nil {
   101  				return errors.E(op, types.UniquePath(localSubPkgPath), err)
   102  			}
   103  			if !hasDiff {
   104  				d = d.Difference(kptfileSet)
   105  			}
   106  		}
   107  
   108  		aggDiff.Insert(d.List()...)
   109  	}
   110  	if aggDiff.Len() > 0 {
   111  		return errors.E(op, types.UniquePath(localPath), fmt.Sprintf(
   112  			"local package files have been modified: %v.\n  use a different update --strategy.",
   113  			aggDiff.List()))
   114  	}
   115  	return nil
   116  }
   117  
   118  func hasKfDiff(localPath, orgPath string) (bool, error) {
   119  	const op errors.Op = "update.hasKfDiff"
   120  	localKf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, localPath)
   121  	if err != nil {
   122  		return false, errors.E(op, types.UniquePath(localPath), err)
   123  	}
   124  	localKf.Upstream = nil
   125  	localKf.UpstreamLock = nil
   126  
   127  	_, err = os.Stat(filepath.Join(orgPath, kptfilev1.KptFileName))
   128  	if err != nil {
   129  		if os.IsNotExist(err) {
   130  			// We know that there aren't any Kptfile in the original
   131  			// package, so we ignore the diff if the local Kptfile
   132  			// is just the minimal Kptfile generated automatically.
   133  			isDefault, err := isDefaultKptfile(localKf, filepath.Base(localPath))
   134  			if err != nil {
   135  				return false, errors.E(op, types.UniquePath(localPath), err)
   136  			}
   137  			return !isDefault, nil
   138  		}
   139  		return false, errors.E(op, types.UniquePath(localPath), err)
   140  	}
   141  	orgKf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, orgPath)
   142  	if err != nil {
   143  		return false, errors.E(op, types.UniquePath(localPath), err)
   144  	}
   145  
   146  	orgKf.Name = localKf.Name
   147  	equal, err := kptfileutil.Equal(localKf, orgKf)
   148  	if err != nil {
   149  		return false, errors.E(op, types.UniquePath(localPath), err)
   150  	}
   151  
   152  	return !equal, nil
   153  }
   154  
   155  func isDefaultKptfile(localKf *kptfilev1.KptFile, name string) (bool, error) {
   156  	defaultKf := kptfileutil.DefaultKptfile(name)
   157  	return kptfileutil.Equal(localKf, defaultKf)
   158  }