github.com/sgoings/helm@v2.0.0-alpha.2.0.20170406211108-734e92851ac3+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' cannot be a repository alias. It must be
    63  a URL.
    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{
   112  		out: out,
   113  	}
   114  	cmd := &cobra.Command{
   115  		Use:     "list [flags] CHART",
   116  		Aliases: []string{"ls"},
   117  		Short:   "list the dependencies for the given chart",
   118  		Long:    dependencyListDesc,
   119  		RunE: func(cmd *cobra.Command, args []string) error {
   120  			cp := "."
   121  			if len(args) > 0 {
   122  				cp = args[0]
   123  			}
   124  
   125  			var err error
   126  			dlc.chartpath, err = filepath.Abs(cp)
   127  			if err != nil {
   128  				return err
   129  			}
   130  			return dlc.run()
   131  		},
   132  	}
   133  	return cmd
   134  }
   135  
   136  func (l *dependencyListCmd) run() error {
   137  	c, err := chartutil.Load(l.chartpath)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	r, err := chartutil.LoadRequirements(c)
   143  	if err != nil {
   144  		if err == chartutil.ErrRequirementsNotFound {
   145  			fmt.Fprintf(l.out, "WARNING: no requirements at %s/charts\n", l.chartpath)
   146  			return nil
   147  		}
   148  		return err
   149  	}
   150  
   151  	l.printRequirements(r, l.out)
   152  	fmt.Fprintln(l.out)
   153  	l.printMissing(r, l.out)
   154  	return nil
   155  }
   156  
   157  func (l *dependencyListCmd) dependencyStatus(dep *chartutil.Dependency) string {
   158  	filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
   159  	archives, err := filepath.Glob(filepath.Join(l.chartpath, "charts", filename))
   160  	if err != nil {
   161  		return "bad pattern"
   162  	} else if len(archives) > 1 {
   163  		return "too many matches"
   164  	} else if len(archives) == 1 {
   165  		archive := archives[0]
   166  		if _, err := os.Stat(archive); err == nil {
   167  			c, err := chartutil.Load(archive)
   168  			if err != nil {
   169  				return "corrupt"
   170  			}
   171  			if c.Metadata.Name != dep.Name {
   172  				return "misnamed"
   173  			}
   174  
   175  			if c.Metadata.Version != dep.Version {
   176  				constraint, err := semver.NewConstraint(dep.Version)
   177  				if err != nil {
   178  					return "invalid version"
   179  				}
   180  
   181  				v, err := semver.NewVersion(c.Metadata.Version)
   182  				if err != nil {
   183  					return "invalid version"
   184  				}
   185  
   186  				if constraint.Check(v) {
   187  					return "ok"
   188  				}
   189  				return "wrong version"
   190  			}
   191  			return "ok"
   192  		}
   193  	}
   194  
   195  	folder := filepath.Join(l.chartpath, "charts", dep.Name)
   196  	if fi, err := os.Stat(folder); err != nil {
   197  		return "missing"
   198  	} else if !fi.IsDir() {
   199  		return "mispackaged"
   200  	}
   201  
   202  	c, err := chartutil.Load(folder)
   203  	if err != nil {
   204  		return "corrupt"
   205  	}
   206  
   207  	if c.Metadata.Name != dep.Name {
   208  		return "misnamed"
   209  	}
   210  
   211  	if c.Metadata.Version != dep.Version {
   212  		constraint, err := semver.NewConstraint(dep.Version)
   213  		if err != nil {
   214  			return "invalid version"
   215  		}
   216  
   217  		v, err := semver.NewVersion(c.Metadata.Version)
   218  		if err != nil {
   219  			return "invalid version"
   220  		}
   221  
   222  		if constraint.Check(v) {
   223  			return "unpacked"
   224  		}
   225  		return "wrong version"
   226  	}
   227  
   228  	return "unpacked"
   229  }
   230  
   231  // printRequirements prints all of the requirements in the yaml file.
   232  func (l *dependencyListCmd) printRequirements(reqs *chartutil.Requirements, out io.Writer) {
   233  	table := uitable.New()
   234  	table.MaxColWidth = 80
   235  	table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
   236  	for _, row := range reqs.Dependencies {
   237  		table.AddRow(row.Name, row.Version, row.Repository, l.dependencyStatus(row))
   238  	}
   239  	fmt.Fprintln(out, table)
   240  }
   241  
   242  // printMissing prints warnings about charts that are present on disk, but are not in the requirements.
   243  func (l *dependencyListCmd) printMissing(reqs *chartutil.Requirements, out io.Writer) {
   244  	folder := filepath.Join(l.chartpath, "charts/*")
   245  	files, err := filepath.Glob(folder)
   246  	if err != nil {
   247  		fmt.Fprintln(l.out, err)
   248  		return
   249  	}
   250  
   251  	for _, f := range files {
   252  		fi, err := os.Stat(f)
   253  		if err != nil {
   254  			fmt.Fprintf(l.out, "Warning: %s\n", err)
   255  		}
   256  		// Skip anything that is not a directory and not a tgz file.
   257  		if !fi.IsDir() && filepath.Ext(f) != ".tgz" {
   258  			continue
   259  		}
   260  		c, err := chartutil.Load(f)
   261  		if err != nil {
   262  			fmt.Fprintf(l.out, "WARNING: %q is not a chart.\n", f)
   263  			continue
   264  		}
   265  		found := false
   266  		for _, d := range reqs.Dependencies {
   267  			if d.Name == c.Metadata.Name {
   268  				found = true
   269  				break
   270  			}
   271  		}
   272  		if !found {
   273  			fmt.Fprintf(l.out, "WARNING: %q is not in requirements.yaml.\n", f)
   274  		}
   275  	}
   276  
   277  }