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