github.com/sdbaiguanghe/helm@v2.16.7+incompatible/pkg/plugin/installer/http_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 "k8s.io/helm/pkg/plugin/installer" 17 18 import ( 19 "archive/tar" 20 "bytes" 21 "compress/gzip" 22 "fmt" 23 "io" 24 "os" 25 "path/filepath" 26 "regexp" 27 "strings" 28 29 fp "github.com/cyphar/filepath-securejoin" 30 31 "k8s.io/helm/pkg/getter" 32 "k8s.io/helm/pkg/helm/environment" 33 "k8s.io/helm/pkg/helm/helmpath" 34 "k8s.io/helm/pkg/plugin/cache" 35 ) 36 37 // HTTPInstaller installs plugins from an archive served by a web server. 38 type HTTPInstaller struct { 39 CacheDir string 40 PluginName string 41 base 42 extractor Extractor 43 getter getter.Getter 44 } 45 46 // TarGzExtractor extracts gzip compressed tar archives 47 type TarGzExtractor struct{} 48 49 // Extractor provides an interface for extracting archives 50 type Extractor interface { 51 Extract(buffer *bytes.Buffer, targetDir string) error 52 } 53 54 // Extractors contains a map of suffixes and matching implementations of extractor to return 55 var Extractors = map[string]Extractor{ 56 ".tar.gz": &TarGzExtractor{}, 57 ".tgz": &TarGzExtractor{}, 58 } 59 60 // NewExtractor creates a new extractor matching the source file name 61 func NewExtractor(source string) (Extractor, error) { 62 for suffix, extractor := range Extractors { 63 if strings.HasSuffix(source, suffix) { 64 return extractor, nil 65 } 66 } 67 return nil, fmt.Errorf("no extractor implemented yet for %s", source) 68 } 69 70 // NewHTTPInstaller creates a new HttpInstaller. 71 func NewHTTPInstaller(source string, home helmpath.Home) (*HTTPInstaller, error) { 72 73 key, err := cache.Key(source) 74 if err != nil { 75 return nil, err 76 } 77 78 extractor, err := NewExtractor(source) 79 if err != nil { 80 return nil, err 81 } 82 83 getConstructor, err := getter.ByScheme("http", environment.EnvSettings{}) 84 if err != nil { 85 return nil, err 86 } 87 88 get, err := getConstructor.New(source, "", "", "") 89 if err != nil { 90 return nil, err 91 } 92 93 i := &HTTPInstaller{ 94 CacheDir: home.Path("cache", "plugins", key), 95 PluginName: stripPluginName(filepath.Base(source)), 96 base: newBase(source, home), 97 extractor: extractor, 98 getter: get, 99 } 100 return i, nil 101 } 102 103 // helper that relies on some sort of convention for plugin name (plugin-name-<version>) 104 func stripPluginName(name string) string { 105 var strippedName string 106 for suffix := range Extractors { 107 if strings.HasSuffix(name, suffix) { 108 strippedName = strings.TrimSuffix(name, suffix) 109 break 110 } 111 } 112 re := regexp.MustCompile(`(.*)-[0-9]+\..*`) 113 return re.ReplaceAllString(strippedName, `$1`) 114 } 115 116 // Install downloads and extracts the tarball into the cache directory and creates a symlink to the plugin directory in $HELM_HOME. 117 // 118 // Implements Installer. 119 func (i *HTTPInstaller) Install() error { 120 121 pluginData, err := i.getter.Get(i.Source) 122 if err != nil { 123 return err 124 } 125 126 err = i.extractor.Extract(pluginData, i.CacheDir) 127 if err != nil { 128 return err 129 } 130 131 if !isPlugin(i.CacheDir) { 132 return ErrMissingMetadata 133 } 134 135 src, err := filepath.Abs(i.CacheDir) 136 if err != nil { 137 return err 138 } 139 140 return i.link(src) 141 } 142 143 // Update updates a local repository 144 // Not implemented for now since tarball most likely will be packaged by version 145 func (i *HTTPInstaller) Update() error { 146 return fmt.Errorf("method Update() not implemented for HttpInstaller") 147 } 148 149 // Override link because we want to use HttpInstaller.Path() not base.Path() 150 func (i *HTTPInstaller) link(from string) error { 151 debug("symlinking %s to %s", from, i.Path()) 152 return os.Symlink(from, i.Path()) 153 } 154 155 // Path is overridden because we want to join on the plugin name not the file name 156 func (i HTTPInstaller) Path() string { 157 if i.base.Source == "" { 158 return "" 159 } 160 return filepath.Join(i.base.HelmHome.Plugins(), i.PluginName) 161 } 162 163 // Extract extracts compressed archives 164 // 165 // Implements Extractor. 166 func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { 167 uncompressedStream, err := gzip.NewReader(buffer) 168 if err != nil { 169 return err 170 } 171 172 tarReader := tar.NewReader(uncompressedStream) 173 174 os.MkdirAll(targetDir, 0755) 175 176 for true { 177 header, err := tarReader.Next() 178 179 if err == io.EOF { 180 break 181 } 182 183 if err != nil { 184 return err 185 } 186 187 path, err := fp.SecureJoin(targetDir, header.Name) 188 if err != nil { 189 return err 190 } 191 192 switch header.Typeflag { 193 case tar.TypeDir: 194 if err := os.Mkdir(path, 0755); err != nil { 195 return err 196 } 197 case tar.TypeReg: 198 outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) 199 if err != nil { 200 return err 201 } 202 if _, err := io.Copy(outFile, tarReader); err != nil { 203 outFile.Close() 204 return err 205 } 206 outFile.Close() 207 default: 208 return fmt.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) 209 } 210 } 211 212 return nil 213 214 }