github.com/x-helm/helm@v3.0.0-beta.3+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) (*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 get, err := getter.All(new(cli.EnvSettings)).ByScheme("http") 83 if err != nil { 84 return nil, err 85 } 86 87 i := &HTTPInstaller{ 88 CacheDir: helmpath.CachePath("plugins", key), 89 PluginName: stripPluginName(filepath.Base(source)), 90 base: newBase(source), 91 extractor: extractor, 92 getter: get, 93 } 94 return i, nil 95 } 96 97 // helper that relies on some sort of convention for plugin name (plugin-name-<version>) 98 func stripPluginName(name string) string { 99 var strippedName string 100 for suffix := range Extractors { 101 if strings.HasSuffix(name, suffix) { 102 strippedName = strings.TrimSuffix(name, suffix) 103 break 104 } 105 } 106 re := regexp.MustCompile(`(.*)-[0-9]+\..*`) 107 return re.ReplaceAllString(strippedName, `$1`) 108 } 109 110 // Install downloads and extracts the tarball into the cache directory 111 // and creates a symlink to the plugin directory. 112 // 113 // Implements Installer. 114 func (i *HTTPInstaller) Install() error { 115 116 pluginData, err := i.getter.Get(i.Source) 117 if err != nil { 118 return err 119 } 120 121 err = i.extractor.Extract(pluginData, i.CacheDir) 122 if err != nil { 123 return err 124 } 125 126 if !isPlugin(i.CacheDir) { 127 return ErrMissingMetadata 128 } 129 130 src, err := filepath.Abs(i.CacheDir) 131 if err != nil { 132 return err 133 } 134 135 return i.link(src) 136 } 137 138 // Update updates a local repository 139 // Not implemented for now since tarball most likely will be packaged by version 140 func (i *HTTPInstaller) Update() error { 141 return errors.Errorf("method Update() not implemented for HttpInstaller") 142 } 143 144 // Override link because we want to use HttpInstaller.Path() not base.Path() 145 func (i *HTTPInstaller) link(from string) error { 146 debug("symlinking %s to %s", from, i.Path()) 147 return os.Symlink(from, i.Path()) 148 } 149 150 // Path is overridden because we want to join on the plugin name not the file name 151 func (i HTTPInstaller) Path() string { 152 if i.base.Source == "" { 153 return "" 154 } 155 return helmpath.DataPath("plugins", i.PluginName) 156 } 157 158 // Extract extracts compressed archives 159 // 160 // Implements Extractor. 161 func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { 162 uncompressedStream, err := gzip.NewReader(buffer) 163 if err != nil { 164 return err 165 } 166 167 tarReader := tar.NewReader(uncompressedStream) 168 169 os.MkdirAll(targetDir, 0755) 170 171 for { 172 header, err := tarReader.Next() 173 174 if err == io.EOF { 175 break 176 } 177 178 if err != nil { 179 return err 180 } 181 182 path := filepath.Join(targetDir, header.Name) 183 184 switch header.Typeflag { 185 case tar.TypeDir: 186 if err := os.Mkdir(path, 0755); err != nil { 187 return err 188 } 189 case tar.TypeReg: 190 outFile, err := os.Create(path) 191 if err != nil { 192 return err 193 } 194 if _, err := io.Copy(outFile, tarReader); err != nil { 195 outFile.Close() 196 return err 197 } 198 outFile.Close() 199 default: 200 return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) 201 } 202 } 203 204 return nil 205 206 }