github.com/darkowlzz/helm@v2.5.1-0.20171213183701-6707fe0468d4+incompatible/pkg/chartutil/load.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     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 chartutil
    18  
    19  import (
    20  	"archive/tar"
    21  	"bytes"
    22  	"compress/gzip"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  
    31  	"github.com/golang/protobuf/ptypes/any"
    32  
    33  	"k8s.io/helm/pkg/ignore"
    34  	"k8s.io/helm/pkg/proto/hapi/chart"
    35  )
    36  
    37  // Load takes a string name, tries to resolve it to a file or directory, and then loads it.
    38  //
    39  // This is the preferred way to load a chart. It will discover the chart encoding
    40  // and hand off to the appropriate chart reader.
    41  //
    42  // If a .helmignore file is present, the directory loader will skip loading any files
    43  // matching it. But .helmignore is not evaluated when reading out of an archive.
    44  func Load(name string) (*chart.Chart, error) {
    45  	fi, err := os.Stat(name)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	if fi.IsDir() {
    50  		if validChart, err := IsChartDir(name); !validChart {
    51  			return nil, err
    52  		}
    53  		return LoadDir(name)
    54  	}
    55  	return LoadFile(name)
    56  }
    57  
    58  // BufferedFile represents an archive file buffered for later processing.
    59  type BufferedFile struct {
    60  	Name string
    61  	Data []byte
    62  }
    63  
    64  // LoadArchive loads from a reader containing a compressed tar archive.
    65  func LoadArchive(in io.Reader) (*chart.Chart, error) {
    66  	unzipped, err := gzip.NewReader(in)
    67  	if err != nil {
    68  		return &chart.Chart{}, err
    69  	}
    70  	defer unzipped.Close()
    71  
    72  	files := []*BufferedFile{}
    73  	tr := tar.NewReader(unzipped)
    74  	for {
    75  		b := bytes.NewBuffer(nil)
    76  		hd, err := tr.Next()
    77  		if err == io.EOF {
    78  			break
    79  		}
    80  		if err != nil {
    81  			return &chart.Chart{}, err
    82  		}
    83  
    84  		if hd.FileInfo().IsDir() {
    85  			// Use this instead of hd.Typeflag because we don't have to do any
    86  			// inference chasing.
    87  			continue
    88  		}
    89  
    90  		// Archive could contain \ if generated on Windows
    91  		delimiter := "/"
    92  		if strings.ContainsRune(hd.Name, '\\') {
    93  			delimiter = "\\"
    94  		}
    95  
    96  		parts := strings.Split(hd.Name, delimiter)
    97  		n := strings.Join(parts[1:], delimiter)
    98  
    99  		// Normalize the path to the / delimiter
   100  		n = strings.Replace(n, delimiter, "/", -1)
   101  
   102  		if parts[0] == "Chart.yaml" {
   103  			return nil, errors.New("chart yaml not in base directory")
   104  		}
   105  
   106  		if _, err := io.Copy(b, tr); err != nil {
   107  			return &chart.Chart{}, err
   108  		}
   109  
   110  		files = append(files, &BufferedFile{Name: n, Data: b.Bytes()})
   111  		b.Reset()
   112  	}
   113  
   114  	if len(files) == 0 {
   115  		return nil, errors.New("no files in chart archive")
   116  	}
   117  
   118  	return LoadFiles(files)
   119  }
   120  
   121  // LoadFiles loads from in-memory files.
   122  func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
   123  	c := &chart.Chart{}
   124  	subcharts := map[string][]*BufferedFile{}
   125  
   126  	for _, f := range files {
   127  		if f.Name == "Chart.yaml" {
   128  			m, err := UnmarshalChartfile(f.Data)
   129  			if err != nil {
   130  				return c, err
   131  			}
   132  			c.Metadata = m
   133  		} else if f.Name == "values.toml" {
   134  			return c, errors.New("values.toml is illegal as of 2.0.0-alpha.2")
   135  		} else if f.Name == "values.yaml" {
   136  			c.Values = &chart.Config{Raw: string(f.Data)}
   137  		} else if strings.HasPrefix(f.Name, "templates/") {
   138  			c.Templates = append(c.Templates, &chart.Template{Name: f.Name, Data: f.Data})
   139  		} else if strings.HasPrefix(f.Name, "charts/") {
   140  			if filepath.Ext(f.Name) == ".prov" {
   141  				c.Files = append(c.Files, &any.Any{TypeUrl: f.Name, Value: f.Data})
   142  				continue
   143  			}
   144  			cname := strings.TrimPrefix(f.Name, "charts/")
   145  			if strings.IndexAny(cname, "._") == 0 {
   146  				// Ignore charts/ that start with . or _.
   147  				continue
   148  			}
   149  			parts := strings.SplitN(cname, "/", 2)
   150  			scname := parts[0]
   151  			subcharts[scname] = append(subcharts[scname], &BufferedFile{Name: cname, Data: f.Data})
   152  		} else {
   153  			c.Files = append(c.Files, &any.Any{TypeUrl: f.Name, Value: f.Data})
   154  		}
   155  	}
   156  
   157  	// Ensure that we got a Chart.yaml file
   158  	if c.Metadata == nil {
   159  		return c, errors.New("chart metadata (Chart.yaml) missing")
   160  	}
   161  	if c.Metadata.Name == "" {
   162  		return c, errors.New("invalid chart (Chart.yaml): name must not be empty")
   163  	}
   164  
   165  	for n, files := range subcharts {
   166  		var sc *chart.Chart
   167  		var err error
   168  		if strings.IndexAny(n, "_.") == 0 {
   169  			continue
   170  		} else if filepath.Ext(n) == ".tgz" {
   171  			file := files[0]
   172  			if file.Name != n {
   173  				return c, fmt.Errorf("error unpacking tar in %s: expected %s, got %s", c.Metadata.Name, n, file.Name)
   174  			}
   175  			// Untar the chart and add to c.Dependencies
   176  			b := bytes.NewBuffer(file.Data)
   177  			sc, err = LoadArchive(b)
   178  		} else {
   179  			// We have to trim the prefix off of every file, and ignore any file
   180  			// that is in charts/, but isn't actually a chart.
   181  			buff := make([]*BufferedFile, 0, len(files))
   182  			for _, f := range files {
   183  				parts := strings.SplitN(f.Name, "/", 2)
   184  				if len(parts) < 2 {
   185  					continue
   186  				}
   187  				f.Name = parts[1]
   188  				buff = append(buff, f)
   189  			}
   190  			sc, err = LoadFiles(buff)
   191  		}
   192  
   193  		if err != nil {
   194  			return c, fmt.Errorf("error unpacking %s in %s: %s", n, c.Metadata.Name, err)
   195  		}
   196  
   197  		c.Dependencies = append(c.Dependencies, sc)
   198  	}
   199  
   200  	return c, nil
   201  }
   202  
   203  // LoadFile loads from an archive file.
   204  func LoadFile(name string) (*chart.Chart, error) {
   205  	if fi, err := os.Stat(name); err != nil {
   206  		return nil, err
   207  	} else if fi.IsDir() {
   208  		return nil, errors.New("cannot load a directory")
   209  	}
   210  
   211  	raw, err := os.Open(name)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	defer raw.Close()
   216  
   217  	return LoadArchive(raw)
   218  }
   219  
   220  // LoadDir loads from a directory.
   221  //
   222  // This loads charts only from directories.
   223  func LoadDir(dir string) (*chart.Chart, error) {
   224  	topdir, err := filepath.Abs(dir)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	// Just used for errors.
   230  	c := &chart.Chart{}
   231  
   232  	rules := ignore.Empty()
   233  	ifile := filepath.Join(topdir, ignore.HelmIgnore)
   234  	if _, err := os.Stat(ifile); err == nil {
   235  		r, err := ignore.ParseFile(ifile)
   236  		if err != nil {
   237  			return c, err
   238  		}
   239  		rules = r
   240  	}
   241  	rules.AddDefaults()
   242  
   243  	files := []*BufferedFile{}
   244  	topdir += string(filepath.Separator)
   245  
   246  	walk := func(name string, fi os.FileInfo, err error) error {
   247  		n := strings.TrimPrefix(name, topdir)
   248  		if n == "" {
   249  			// No need to process top level. Avoid bug with helmignore .* matching
   250  			// empty names. See issue 1779.
   251  			return nil
   252  		}
   253  
   254  		// Normalize to / since it will also work on Windows
   255  		n = filepath.ToSlash(n)
   256  
   257  		if err != nil {
   258  			return err
   259  		}
   260  		if fi.IsDir() {
   261  			// Directory-based ignore rules should involve skipping the entire
   262  			// contents of that directory.
   263  			if rules.Ignore(n, fi) {
   264  				return filepath.SkipDir
   265  			}
   266  			return nil
   267  		}
   268  
   269  		// If a .helmignore file matches, skip this file.
   270  		if rules.Ignore(n, fi) {
   271  			return nil
   272  		}
   273  
   274  		data, err := ioutil.ReadFile(name)
   275  		if err != nil {
   276  			return fmt.Errorf("error reading %s: %s", n, err)
   277  		}
   278  
   279  		files = append(files, &BufferedFile{Name: n, Data: data})
   280  		return nil
   281  	}
   282  	if err = filepath.Walk(topdir, symWalk(topdir, "", walk)); err != nil {
   283  		return c, err
   284  	}
   285  
   286  	return LoadFiles(files)
   287  }
   288  
   289  // symWalk walks topdir with optional symbolic link dir, symdir. The symdir will
   290  // be used as the path name sent to walkFn.
   291  func symWalk(topdir, symdir string, walkFn filepath.WalkFunc) filepath.WalkFunc {
   292  	return func(name string, fi os.FileInfo, err error) error {
   293  		// Recover the symbolic path instead of the real path.
   294  		if symdir != "" {
   295  			relative, err := filepath.Rel(topdir, name)
   296  			if err != nil {
   297  				return err
   298  			}
   299  			name = filepath.Join(symdir, relative)
   300  		}
   301  
   302  		// Recursively walk symlinked directories.
   303  		if isSymlink(fi) {
   304  			resolved, err := filepath.EvalSymlinks(name)
   305  			if err != nil {
   306  				return fmt.Errorf("error evaluating symlink %s: %s", name, err)
   307  			}
   308  			if fi, err = os.Lstat(resolved); err != nil {
   309  				return err
   310  			}
   311  			if fi.IsDir() {
   312  				return filepath.Walk(resolved, symWalk(resolved, name, walkFn))
   313  			}
   314  		}
   315  
   316  		return walkFn(name, fi, err)
   317  	}
   318  }
   319  
   320  func isSymlink(fi os.FileInfo) bool {
   321  	return fi.Mode()&os.ModeSymlink != 0
   322  }