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  }