github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/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 "github.com/stefanmcshane/helm/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 "github.com/stefanmcshane/helm/internal/third_party/dep/fs" 27 "github.com/stefanmcshane/helm/pkg/helmpath" 28 "github.com/stefanmcshane/helm/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 }