github.com/strongmonkey/helm@v2.7.2+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  	err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error {
   247  		n := strings.TrimPrefix(name, topdir)
   248  
   249  		// Normalize to / since it will also work on Windows
   250  		n = filepath.ToSlash(n)
   251  
   252  		if err != nil {
   253  			return err
   254  		}
   255  		if fi.IsDir() {
   256  			// Directory-based ignore rules should involve skipping the entire
   257  			// contents of that directory.
   258  			if rules.Ignore(n, fi) {
   259  				return filepath.SkipDir
   260  			}
   261  			return nil
   262  		}
   263  
   264  		// If a .helmignore file matches, skip this file.
   265  		if rules.Ignore(n, fi) {
   266  			return nil
   267  		}
   268  
   269  		data, err := ioutil.ReadFile(name)
   270  		if err != nil {
   271  			return fmt.Errorf("error reading %s: %s", n, err)
   272  		}
   273  
   274  		files = append(files, &BufferedFile{Name: n, Data: data})
   275  		return nil
   276  	})
   277  	if err != nil {
   278  		return c, err
   279  	}
   280  
   281  	return LoadFiles(files)
   282  }