github.com/Beeketing/helm@v2.12.1+incompatible/pkg/chartutil/load.go (about)

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