github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/chart/loader/load.go (about)

     1  /*
     2  Copyright The Helm Authors.
     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 loader
    18  
    19  import (
    20  	"bytes"
    21  	"log"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/pkg/errors"
    27  	"sigs.k8s.io/yaml"
    28  
    29  	"github.com/stefanmcshane/helm/pkg/chart"
    30  )
    31  
    32  // ChartLoader loads a chart.
    33  type ChartLoader interface {
    34  	Load() (*chart.Chart, error)
    35  }
    36  
    37  // Loader returns a new ChartLoader appropriate for the given chart name
    38  func Loader(name string) (ChartLoader, error) {
    39  	fi, err := os.Stat(name)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	if fi.IsDir() {
    44  		return DirLoader(name), nil
    45  	}
    46  	return FileLoader(name), nil
    47  
    48  }
    49  
    50  // Load takes a string name, tries to resolve it to a file or directory, and then loads it.
    51  //
    52  // This is the preferred way to load a chart. It will discover the chart encoding
    53  // and hand off to the appropriate chart reader.
    54  //
    55  // If a .helmignore file is present, the directory loader will skip loading any files
    56  // matching it. But .helmignore is not evaluated when reading out of an archive.
    57  func Load(name string) (*chart.Chart, error) {
    58  	l, err := Loader(name)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	return l.Load()
    63  }
    64  
    65  // BufferedFile represents an archive file buffered for later processing.
    66  type BufferedFile struct {
    67  	Name string
    68  	Data []byte
    69  }
    70  
    71  // LoadFiles loads from in-memory files.
    72  func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
    73  	c := new(chart.Chart)
    74  	subcharts := make(map[string][]*BufferedFile)
    75  
    76  	// do not rely on assumed ordering of files in the chart and crash
    77  	// if Chart.yaml was not coming early enough to initialize metadata
    78  	for _, f := range files {
    79  		c.Raw = append(c.Raw, &chart.File{Name: f.Name, Data: f.Data})
    80  		if f.Name == "Chart.yaml" {
    81  			if c.Metadata == nil {
    82  				c.Metadata = new(chart.Metadata)
    83  			}
    84  			if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
    85  				return c, errors.Wrap(err, "cannot load Chart.yaml")
    86  			}
    87  			// NOTE(bacongobbler): while the chart specification says that APIVersion must be set,
    88  			// Helm 2 accepted charts that did not provide an APIVersion in their chart metadata.
    89  			// Because of that, if APIVersion is unset, we should assume we're loading a v1 chart.
    90  			if c.Metadata.APIVersion == "" {
    91  				c.Metadata.APIVersion = chart.APIVersionV1
    92  			}
    93  		}
    94  	}
    95  	for _, f := range files {
    96  		switch {
    97  		case f.Name == "Chart.yaml":
    98  			// already processed
    99  			continue
   100  		case f.Name == "Chart.lock":
   101  			c.Lock = new(chart.Lock)
   102  			if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
   103  				return c, errors.Wrap(err, "cannot load Chart.lock")
   104  			}
   105  		case f.Name == "values.yaml":
   106  			c.Values = make(map[string]interface{})
   107  			if err := yaml.Unmarshal(f.Data, &c.Values); err != nil {
   108  				return c, errors.Wrap(err, "cannot load values.yaml")
   109  			}
   110  		case f.Name == "values.schema.json":
   111  			c.Schema = f.Data
   112  
   113  		// Deprecated: requirements.yaml is deprecated use Chart.yaml.
   114  		// We will handle it for you because we are nice people
   115  		case f.Name == "requirements.yaml":
   116  			if c.Metadata == nil {
   117  				c.Metadata = new(chart.Metadata)
   118  			}
   119  			if c.Metadata.APIVersion != chart.APIVersionV1 {
   120  				log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.")
   121  			}
   122  			if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
   123  				return c, errors.Wrap(err, "cannot load requirements.yaml")
   124  			}
   125  			if c.Metadata.APIVersion == chart.APIVersionV1 {
   126  				c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
   127  			}
   128  		// Deprecated: requirements.lock is deprecated use Chart.lock.
   129  		case f.Name == "requirements.lock":
   130  			c.Lock = new(chart.Lock)
   131  			if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
   132  				return c, errors.Wrap(err, "cannot load requirements.lock")
   133  			}
   134  			if c.Metadata == nil {
   135  				c.Metadata = new(chart.Metadata)
   136  			}
   137  			if c.Metadata.APIVersion == chart.APIVersionV1 {
   138  				c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
   139  			}
   140  
   141  		case strings.HasPrefix(f.Name, "templates/"):
   142  			c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data})
   143  		case strings.HasPrefix(f.Name, "charts/"):
   144  			if filepath.Ext(f.Name) == ".prov" {
   145  				c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
   146  				continue
   147  			}
   148  
   149  			fname := strings.TrimPrefix(f.Name, "charts/")
   150  			cname := strings.SplitN(fname, "/", 2)[0]
   151  			subcharts[cname] = append(subcharts[cname], &BufferedFile{Name: fname, Data: f.Data})
   152  		default:
   153  			c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
   154  		}
   155  	}
   156  
   157  	if c.Metadata == nil {
   158  		return c, errors.New("Chart.yaml file is missing")
   159  	}
   160  
   161  	if err := c.Validate(); err != nil {
   162  		return c, err
   163  	}
   164  
   165  	for n, files := range subcharts {
   166  		var sc *chart.Chart
   167  		var err error
   168  		switch {
   169  		case strings.IndexAny(n, "_.") == 0:
   170  			continue
   171  		case filepath.Ext(n) == ".tgz":
   172  			file := files[0]
   173  			if file.Name != n {
   174  				return c, errors.Errorf("error unpacking tar in %s: expected %s, got %s", c.Name(), n, file.Name)
   175  			}
   176  			// Untar the chart and add to c.Dependencies
   177  			sc, err = LoadArchive(bytes.NewBuffer(file.Data))
   178  		default:
   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, errors.Wrapf(err, "error unpacking %s in %s", n, c.Name())
   195  		}
   196  		c.AddDependency(sc)
   197  	}
   198  
   199  	return c, nil
   200  }