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