...

Source file src/helm.sh/helm/v3/pkg/plugin/installer/vcs_installer.go

Documentation: helm.sh/helm/v3/pkg/plugin/installer

     1  /*
     2  Copyright The Helm Authors.
     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  
    16  package installer // import "helm.sh/helm/v3/pkg/plugin/installer"
    17  
    18  import (
    19  	"os"
    20  	"sort"
    21  
    22  	"github.com/Masterminds/semver/v3"
    23  	"github.com/Masterminds/vcs"
    24  	"github.com/pkg/errors"
    25  
    26  	"helm.sh/helm/v3/internal/third_party/dep/fs"
    27  	"helm.sh/helm/v3/pkg/helmpath"
    28  	"helm.sh/helm/v3/pkg/plugin/cache"
    29  )
    30  
    31  // VCSInstaller installs plugins from remote a repository.
    32  type VCSInstaller struct {
    33  	Repo    vcs.Repo
    34  	Version string
    35  	base
    36  }
    37  
    38  func existingVCSRepo(location string) (Installer, error) {
    39  	repo, err := vcs.NewRepo("", location)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	i := &VCSInstaller{
    44  		Repo: repo,
    45  		base: newBase(repo.Remote()),
    46  	}
    47  	return i, nil
    48  }
    49  
    50  // NewVCSInstaller creates a new VCSInstaller.
    51  func NewVCSInstaller(source, version string) (*VCSInstaller, error) {
    52  	key, err := cache.Key(source)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	cachedpath := helmpath.CachePath("plugins", key)
    57  	repo, err := vcs.NewRepo(source, cachedpath)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	i := &VCSInstaller{
    62  		Repo:    repo,
    63  		Version: version,
    64  		base:    newBase(source),
    65  	}
    66  	return i, err
    67  }
    68  
    69  // Install clones a remote repository and installs into the plugin directory.
    70  //
    71  // Implements Installer.
    72  func (i *VCSInstaller) Install() error {
    73  	if err := i.sync(i.Repo); err != nil {
    74  		return err
    75  	}
    76  
    77  	ref, err := i.solveVersion(i.Repo)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	if ref != "" {
    82  		if err := i.setVersion(i.Repo, ref); err != nil {
    83  			return err
    84  		}
    85  	}
    86  
    87  	if !isPlugin(i.Repo.LocalPath()) {
    88  		return ErrMissingMetadata
    89  	}
    90  
    91  	debug("copying %s to %s", i.Repo.LocalPath(), i.Path())
    92  	return fs.CopyDir(i.Repo.LocalPath(), i.Path())
    93  }
    94  
    95  // Update updates a remote repository
    96  func (i *VCSInstaller) Update() error {
    97  	debug("updating %s", i.Repo.Remote())
    98  	if i.Repo.IsDirty() {
    99  		return errors.New("plugin repo was modified")
   100  	}
   101  	if err := i.Repo.Update(); err != nil {
   102  		return err
   103  	}
   104  	if !isPlugin(i.Repo.LocalPath()) {
   105  		return ErrMissingMetadata
   106  	}
   107  	return nil
   108  }
   109  
   110  func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
   111  	if i.Version == "" {
   112  		return "", nil
   113  	}
   114  
   115  	if repo.IsReference(i.Version) {
   116  		return i.Version, nil
   117  	}
   118  
   119  	// Create the constraint first to make sure it's valid before
   120  	// working on the repo.
   121  	constraint, err := semver.NewConstraint(i.Version)
   122  	if err != nil {
   123  		return "", err
   124  	}
   125  
   126  	// Get the tags
   127  	refs, err := repo.Tags()
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  	debug("found refs: %s", refs)
   132  
   133  	// Convert and filter the list to semver.Version instances
   134  	semvers := getSemVers(refs)
   135  
   136  	// Sort semver list
   137  	sort.Sort(sort.Reverse(semver.Collection(semvers)))
   138  	for _, v := range semvers {
   139  		if constraint.Check(v) {
   140  			// If the constraint passes get the original reference
   141  			ver := v.Original()
   142  			debug("setting to %s", ver)
   143  			return ver, nil
   144  		}
   145  	}
   146  
   147  	return "", errors.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote())
   148  }
   149  
   150  // setVersion attempts to checkout the version
   151  func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error {
   152  	debug("setting version to %q", i.Version)
   153  	return repo.UpdateVersion(ref)
   154  }
   155  
   156  // sync will clone or update a remote repo.
   157  func (i *VCSInstaller) sync(repo vcs.Repo) error {
   158  	if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) {
   159  		debug("cloning %s to %s", repo.Remote(), repo.LocalPath())
   160  		return repo.Get()
   161  	}
   162  	debug("updating %s", repo.Remote())
   163  	return repo.Update()
   164  }
   165  
   166  // Filter a list of versions to only included semantic versions. The response
   167  // is a mapping of the original version to the semantic version.
   168  func getSemVers(refs []string) []*semver.Version {
   169  	var sv []*semver.Version
   170  	for _, r := range refs {
   171  		if v, err := semver.NewVersion(r); err == nil {
   172  			sv = append(sv, v)
   173  		}
   174  	}
   175  	return sv
   176  }
   177  

View as plain text