github.com/x-helm/helm@v3.0.0-beta.3+incompatible/pkg/plugin/installer/vcs_installer.go (about)

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