github.com/lrills/helm@v2.8.1+incompatible/cmd/helm/dependency.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 main
    17  
    18  import (
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/Masterminds/semver"
    25  	"github.com/gosuri/uitable"
    26  	"github.com/spf13/cobra"
    27  
    28  	"k8s.io/helm/pkg/chartutil"
    29  )
    30  
    31  const dependencyDesc = `
    32  Manage the dependencies of a chart.
    33  
    34  Helm charts store their dependencies in 'charts/'. For chart developers, it is
    35  often easier to manage a single dependency file ('requirements.yaml')
    36  which declares all dependencies.
    37  
    38  The dependency commands operate on that file, making it easy to synchronize
    39  between the desired dependencies and the actual dependencies stored in the
    40  'charts/' directory.
    41  
    42  A 'requirements.yaml' file is a YAML file in which developers can declare chart
    43  dependencies, along with the location of the chart and the desired version.
    44  For example, this requirements file declares two dependencies:
    45  
    46      # requirements.yaml
    47      dependencies:
    48      - name: nginx
    49        version: "1.2.3"
    50        repository: "https://example.com/charts"
    51      - name: memcached
    52        version: "3.2.1"
    53        repository: "https://another.example.com/charts"
    54  
    55  The 'name' should be the name of a chart, where that name must match the name
    56  in that chart's 'Chart.yaml' file.
    57  
    58  The 'version' field should contain a semantic version or version range.
    59  
    60  The 'repository' URL should point to a Chart Repository. Helm expects that by
    61  appending '/index.yaml' to the URL, it should be able to retrieve the chart
    62  repository's index. Note: 'repository' can be an alias. The alias must start
    63  with 'alias:' or '@'.
    64  
    65  Starting from 2.2.0, repository can be defined as the path to the directory of
    66  the dependency charts stored locally. The path should start with a prefix of
    67  "file://". For example,
    68  
    69      # requirements.yaml
    70      dependencies:
    71      - name: nginx
    72        version: "1.2.3"
    73        repository: "file://../dependency_chart/nginx"
    74  
    75  If the dependency chart is retrieved locally, it is not required to have the
    76  repository added to helm by "helm add repo". Version matching is also supported
    77  for this case.
    78  `
    79  
    80  const dependencyListDesc = `
    81  List all of the dependencies declared in a chart.
    82  
    83  This can take chart archives and chart directories as input. It will not alter
    84  the contents of a chart.
    85  
    86  This will produce an error if the chart cannot be loaded. It will emit a warning
    87  if it cannot find a requirements.yaml.
    88  `
    89  
    90  func newDependencyCmd(out io.Writer) *cobra.Command {
    91  	cmd := &cobra.Command{
    92  		Use:     "dependency update|build|list",
    93  		Aliases: []string{"dep", "dependencies"},
    94  		Short:   "manage a chart's dependencies",
    95  		Long:    dependencyDesc,
    96  	}
    97  
    98  	cmd.AddCommand(newDependencyListCmd(out))
    99  	cmd.AddCommand(newDependencyUpdateCmd(out))
   100  	cmd.AddCommand(newDependencyBuildCmd(out))
   101  
   102  	return cmd
   103  }
   104  
   105  type dependencyListCmd struct {
   106  	out       io.Writer
   107  	chartpath string
   108  }
   109  
   110  func newDependencyListCmd(out io.Writer) *cobra.Command {
   111  	dlc := &dependencyListCmd{out: out}
   112  
   113  	cmd := &cobra.Command{
   114  		Use:     "list [flags] CHART",
   115  		Aliases: []string{"ls"},
   116  		Short:   "list the dependencies for the given chart",
   117  		Long:    dependencyListDesc,
   118  		RunE: func(cmd *cobra.Command, args []string) error {
   119  			cp := "."
   120  			if len(args) > 0 {
   121  				cp = args[0]
   122  			}
   123  
   124  			var err error
   125  			dlc.chartpath, err = filepath.Abs(cp)
   126  			if err != nil {
   127  				return err
   128  			}
   129  			return dlc.run()
   130  		},
   131  	}
   132  	return cmd
   133  }
   134  
   135  func (l *dependencyListCmd) run() error {
   136  	c, err := chartutil.Load(l.chartpath)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	r, err := chartutil.LoadRequirements(c)
   142  	if err != nil {
   143  		if err == chartutil.ErrRequirementsNotFound {
   144  			fmt.Fprintf(l.out, "WARNING: no requirements at %s/charts\n", l.chartpath)
   145  			return nil
   146  		}
   147  		return err
   148  	}
   149  
   150  	l.printRequirements(r, l.out)
   151  	fmt.Fprintln(l.out)
   152  	l.printMissing(r)
   153  	return nil
   154  }
   155  
   156  func (l *dependencyListCmd) dependencyStatus(dep *chartutil.Dependency) string {
   157  	filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
   158  	archives, err := filepath.Glob(filepath.Join(l.chartpath, "charts", filename))
   159  	if err != nil {
   160  		return "bad pattern"
   161  	} else if len(archives) > 1 {
   162  		return "too many matches"
   163  	} else if len(archives) == 1 {
   164  		archive := archives[0]
   165  		if _, err := os.Stat(archive); err == nil {
   166  			c, err := chartutil.Load(archive)
   167  			if err != nil {
   168  				return "corrupt"
   169  			}
   170  			if c.Metadata.Name != dep.Name {
   171  				return "misnamed"
   172  			}
   173  
   174  			if c.Metadata.Version != dep.Version {
   175  				constraint, err := semver.NewConstraint(dep.Version)
   176  				if err != nil {
   177  					return "invalid version"
   178  				}
   179  
   180  				v, err := semver.NewVersion(c.Metadata.Version)
   181  				if err != nil {
   182  					return "invalid version"
   183  				}
   184  
   185  				if constraint.Check(v) {
   186  					return "ok"
   187  				}
   188  				return "wrong version"
   189  			}
   190  			return "ok"
   191  		}
   192  	}
   193  
   194  	folder := filepath.Join(l.chartpath, "charts", dep.Name)
   195  	if fi, err := os.Stat(folder); err != nil {
   196  		return "missing"
   197  	} else if !fi.IsDir() {
   198  		return "mispackaged"
   199  	}
   200  
   201  	c, err := chartutil.Load(folder)
   202  	if err != nil {
   203  		return "corrupt"
   204  	}
   205  
   206  	if c.Metadata.Name != dep.Name {
   207  		return "misnamed"
   208  	}
   209  
   210  	if c.Metadata.Version != dep.Version {
   211  		constraint, err := semver.NewConstraint(dep.Version)
   212  		if err != nil {
   213  			return "invalid version"
   214  		}
   215  
   216  		v, err := semver.NewVersion(c.Metadata.Version)
   217  		if err != nil {
   218  			return "invalid version"
   219  		}
   220  
   221  		if constraint.Check(v) {
   222  			return "unpacked"
   223  		}
   224  		return "wrong version"
   225  	}
   226  
   227  	return "unpacked"
   228  }
   229  
   230  // printRequirements prints all of the requirements in the yaml file.
   231  func (l *dependencyListCmd) printRequirements(reqs *chartutil.Requirements, out io.Writer) {
   232  	table := uitable.New()
   233  	table.MaxColWidth = 80
   234  	table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
   235  	for _, row := range reqs.Dependencies {
   236  		table.AddRow(row.Name, row.Version, row.Repository, l.dependencyStatus(row))
   237  	}
   238  	fmt.Fprintln(out, table)
   239  }
   240  
   241  // printMissing prints warnings about charts that are present on disk, but are not in the requirements.
   242  func (l *dependencyListCmd) printMissing(reqs *chartutil.Requirements) {
   243  	folder := filepath.Join(l.chartpath, "charts/*")
   244  	files, err := filepath.Glob(folder)
   245  	if err != nil {
   246  		fmt.Fprintln(l.out, err)
   247  		return
   248  	}
   249  
   250  	for _, f := range files {
   251  		fi, err := os.Stat(f)
   252  		if err != nil {
   253  			fmt.Fprintf(l.out, "Warning: %s\n", err)
   254  		}
   255  		// Skip anything that is not a directory and not a tgz file.
   256  		if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
   257  			continue
   258  		}
   259  		c, err := chartutil.Load(f)
   260  		if err != nil {
   261  			fmt.Fprintf(l.out, "WARNING: %q is not a chart.\n", f)
   262  			continue
   263  		}
   264  		found := false
   265  		for _, d := range reqs.Dependencies {
   266  			if d.Name == c.Metadata.Name {
   267  				found = true
   268  				break
   269  			}
   270  		}
   271  		if !found {
   272  			fmt.Fprintf(l.out, "WARNING: %q is not in requirements.yaml.\n", f)
   273  		}
   274  	}
   275  
   276  }