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