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