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