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  }