...

Source file src/helm.sh/helm/v3/pkg/plugin/installer/installer.go

Documentation: helm.sh/helm/v3/pkg/plugin/installer

     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
    17  
    18  import (
    19  	"fmt"
    20  	"log"
    21  	"net/http"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	"helm.sh/helm/v3/pkg/plugin"
    29  )
    30  
    31  // ErrMissingMetadata indicates that plugin.yaml is missing.
    32  var ErrMissingMetadata = errors.New("plugin metadata (plugin.yaml) missing")
    33  
    34  // Debug enables verbose output.
    35  var Debug bool
    36  
    37  // Installer provides an interface for installing helm client plugins.
    38  type Installer interface {
    39  	// Install adds a plugin.
    40  	Install() error
    41  	// Path is the directory of the installed plugin.
    42  	Path() string
    43  	// Update updates a plugin.
    44  	Update() error
    45  }
    46  
    47  // Install installs a plugin.
    48  func Install(i Installer) error {
    49  	if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil {
    50  		return err
    51  	}
    52  	if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) {
    53  		return errors.New("plugin already exists")
    54  	}
    55  	return i.Install()
    56  }
    57  
    58  // Update updates a plugin.
    59  func Update(i Installer) error {
    60  	if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) {
    61  		return errors.New("plugin does not exist")
    62  	}
    63  	return i.Update()
    64  }
    65  
    66  // NewForSource determines the correct Installer for the given source.
    67  func NewForSource(source, version string) (Installer, error) {
    68  	// Check if source is a local directory
    69  	if isLocalReference(source) {
    70  		return NewLocalInstaller(source)
    71  	} else if isRemoteHTTPArchive(source) {
    72  		return NewHTTPInstaller(source)
    73  	}
    74  	return NewVCSInstaller(source, version)
    75  }
    76  
    77  // FindSource determines the correct Installer for the given source.
    78  func FindSource(location string) (Installer, error) {
    79  	installer, err := existingVCSRepo(location)
    80  	if err != nil && err.Error() == "Cannot detect VCS" {
    81  		return installer, errors.New("cannot get information about plugin source")
    82  	}
    83  	return installer, err
    84  }
    85  
    86  // isLocalReference checks if the source exists on the filesystem.
    87  func isLocalReference(source string) bool {
    88  	_, err := os.Stat(source)
    89  	return err == nil
    90  }
    91  
    92  // isRemoteHTTPArchive checks if the source is a http/https url and is an archive
    93  //
    94  // It works by checking whether the source looks like a URL and, if it does, running a
    95  // HEAD operation to see if the remote resource is a file that we understand.
    96  func isRemoteHTTPArchive(source string) bool {
    97  	if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") {
    98  		res, err := http.Head(source)
    99  		if err != nil {
   100  			// If we get an error at the network layer, we can't install it. So
   101  			// we return false.
   102  			return false
   103  		}
   104  
   105  		// Next, we look for the content type or content disposition headers to see
   106  		// if they have matching extractors.
   107  		contentType := res.Header.Get("content-type")
   108  		foundSuffix, ok := mediaTypeToExtension(contentType)
   109  		if !ok {
   110  			// Media type not recognized
   111  			return false
   112  		}
   113  
   114  		for suffix := range Extractors {
   115  			if strings.HasSuffix(foundSuffix, suffix) {
   116  				return true
   117  			}
   118  		}
   119  	}
   120  	return false
   121  }
   122  
   123  // isPlugin checks if the directory contains a plugin.yaml file.
   124  func isPlugin(dirname string) bool {
   125  	_, err := os.Stat(filepath.Join(dirname, plugin.PluginFileName))
   126  	return err == nil
   127  }
   128  
   129  var logger = log.New(os.Stderr, "[debug] ", log.Lshortfile)
   130  
   131  func debug(format string, args ...interface{}) {
   132  	if Debug {
   133  		logger.Output(2, fmt.Sprintf(format, args...))
   134  	}
   135  }
   136  

View as plain text