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