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  }