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