...

Source file src/helm.sh/helm/v3/pkg/action/dependency.go

Documentation: helm.sh/helm/v3/pkg/action

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package action
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/Masterminds/semver/v3"
    27  	"github.com/gosuri/uitable"
    28  
    29  	"helm.sh/helm/v3/pkg/chart"
    30  	"helm.sh/helm/v3/pkg/chart/loader"
    31  )
    32  
    33  // Dependency is the action for building a given chart's dependency tree.
    34  //
    35  // It provides the implementation of 'helm dependency' and its respective subcommands.
    36  type Dependency struct {
    37  	Verify      bool
    38  	Keyring     string
    39  	SkipRefresh bool
    40  	ColumnWidth uint
    41  }
    42  
    43  // NewDependency creates a new Dependency object with the given configuration.
    44  func NewDependency() *Dependency {
    45  	return &Dependency{
    46  		ColumnWidth: 80,
    47  	}
    48  }
    49  
    50  // List executes 'helm dependency list'.
    51  func (d *Dependency) List(chartpath string, out io.Writer) error {
    52  	c, err := loader.Load(chartpath)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	if c.Metadata.Dependencies == nil {
    58  		fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(chartpath, "charts"))
    59  		return nil
    60  	}
    61  
    62  	d.printDependencies(chartpath, out, c)
    63  	fmt.Fprintln(out)
    64  	d.printMissing(chartpath, out, c.Metadata.Dependencies)
    65  	return nil
    66  }
    67  
    68  // dependencyStatus returns a string describing the status of a dependency viz a viz the parent chart.
    69  func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, parent *chart.Chart) string {
    70  	filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
    71  
    72  	// If a chart is unpacked, this will check the unpacked chart's `charts/` directory for tarballs.
    73  	// Technically, this is COMPLETELY unnecessary, and should be removed in Helm 4. It is here
    74  	// to preserved backward compatibility. In Helm 2/3, there is a "difference" between
    75  	// the tgz version (which outputs "ok" if it unpacks) and the loaded version (which outputs
    76  	// "unpacked"). Early in Helm 2's history, this would have made a difference. But it no
    77  	// longer does. However, since this code shipped with Helm 3, the output must remain stable
    78  	// until Helm 4.
    79  	switch archives, err := filepath.Glob(filepath.Join(chartpath, "charts", filename)); {
    80  	case err != nil:
    81  		return "bad pattern"
    82  	case len(archives) > 1:
    83  		// See if the second part is a SemVer
    84  		found := []string{}
    85  		for _, arc := range archives {
    86  			// we need to trip the prefix dirs and the extension off.
    87  			filename = strings.TrimSuffix(filepath.Base(arc), ".tgz")
    88  			maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name))
    89  
    90  			if _, err := semver.StrictNewVersion(maybeVersion); err == nil {
    91  				// If the version parsed without an error, it is possibly a valid
    92  				// version.
    93  				found = append(found, arc)
    94  			}
    95  		}
    96  
    97  		if l := len(found); l == 1 {
    98  			// If we get here, we do the same thing as in len(archives) == 1.
    99  			if r := statArchiveForStatus(found[0], dep); r != "" {
   100  				return r
   101  			}
   102  
   103  			// Fall through and look for directories
   104  		} else if l > 1 {
   105  			return "too many matches"
   106  		}
   107  
   108  		// The sanest thing to do here is to fall through and see if we have any directory
   109  		// matches.
   110  
   111  	case len(archives) == 1:
   112  		archive := archives[0]
   113  		if r := statArchiveForStatus(archive, dep); r != "" {
   114  			return r
   115  		}
   116  
   117  	}
   118  	// End unnecessary code.
   119  
   120  	var depChart *chart.Chart
   121  	for _, item := range parent.Dependencies() {
   122  		if item.Name() == dep.Name {
   123  			depChart = item
   124  		}
   125  	}
   126  
   127  	if depChart == nil {
   128  		return "missing"
   129  	}
   130  
   131  	if depChart.Metadata.Version != dep.Version {
   132  		constraint, err := semver.NewConstraint(dep.Version)
   133  		if err != nil {
   134  			return "invalid version"
   135  		}
   136  
   137  		v, err := semver.NewVersion(depChart.Metadata.Version)
   138  		if err != nil {
   139  			return "invalid version"
   140  		}
   141  
   142  		if !constraint.Check(v) {
   143  			return "wrong version"
   144  		}
   145  	}
   146  
   147  	return "unpacked"
   148  }
   149  
   150  // stat an archive and return a message if the stat is successful
   151  //
   152  // This is a refactor of the code originally in dependencyStatus. It is here to
   153  // support legacy behavior, and should be removed in Helm 4.
   154  func statArchiveForStatus(archive string, dep *chart.Dependency) string {
   155  	if _, err := os.Stat(archive); err == nil {
   156  		c, err := loader.Load(archive)
   157  		if err != nil {
   158  			return "corrupt"
   159  		}
   160  		if c.Name() != dep.Name {
   161  			return "misnamed"
   162  		}
   163  
   164  		if c.Metadata.Version != dep.Version {
   165  			constraint, err := semver.NewConstraint(dep.Version)
   166  			if err != nil {
   167  				return "invalid version"
   168  			}
   169  
   170  			v, err := semver.NewVersion(c.Metadata.Version)
   171  			if err != nil {
   172  				return "invalid version"
   173  			}
   174  
   175  			if !constraint.Check(v) {
   176  				return "wrong version"
   177  			}
   178  		}
   179  		return "ok"
   180  	}
   181  	return ""
   182  }
   183  
   184  // printDependencies prints all of the dependencies in the yaml file.
   185  func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) {
   186  	table := uitable.New()
   187  	table.MaxColWidth = d.ColumnWidth
   188  	table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
   189  	for _, row := range c.Metadata.Dependencies {
   190  		table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row, c))
   191  	}
   192  	fmt.Fprintln(out, table)
   193  }
   194  
   195  // printMissing prints warnings about charts that are present on disk, but are
   196  // not in Chart.yaml.
   197  func (d *Dependency) printMissing(chartpath string, out io.Writer, reqs []*chart.Dependency) {
   198  	folder := filepath.Join(chartpath, "charts/*")
   199  	files, err := filepath.Glob(folder)
   200  	if err != nil {
   201  		fmt.Fprintln(out, err)
   202  		return
   203  	}
   204  
   205  	for _, f := range files {
   206  		fi, err := os.Stat(f)
   207  		if err != nil {
   208  			fmt.Fprintf(out, "Warning: %s\n", err)
   209  		}
   210  		// Skip anything that is not a directory and not a tgz file.
   211  		if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
   212  			continue
   213  		}
   214  		c, err := loader.Load(f)
   215  		if err != nil {
   216  			fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f)
   217  			continue
   218  		}
   219  		found := false
   220  		for _, d := range reqs {
   221  			if d.Name == c.Name() {
   222  				found = true
   223  				break
   224  			}
   225  		}
   226  		if !found {
   227  			fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f)
   228  		}
   229  	}
   230  }
   231  

View as plain text