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