github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/chartutil/save.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 chartutil
    18  
    19  import (
    20  	"archive/tar"
    21  	"compress/gzip"
    22  	"encoding/json"
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"time"
    27  
    28  	"github.com/pkg/errors"
    29  	"sigs.k8s.io/yaml"
    30  
    31  	"github.com/stefanmcshane/helm/pkg/chart"
    32  )
    33  
    34  var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
    35  
    36  // SaveDir saves a chart as files in a directory.
    37  //
    38  // This takes the chart name, and creates a new subdirectory inside of the given dest
    39  // directory, writing the chart's contents to that subdirectory.
    40  func SaveDir(c *chart.Chart, dest string) error {
    41  	// Create the chart directory
    42  	outdir := filepath.Join(dest, c.Name())
    43  	if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() {
    44  		return errors.Errorf("file %s already exists and is not a directory", outdir)
    45  	}
    46  	if err := os.MkdirAll(outdir, 0755); err != nil {
    47  		return err
    48  	}
    49  
    50  	// Save the chart file.
    51  	if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil {
    52  		return err
    53  	}
    54  
    55  	// Save values.yaml
    56  	for _, f := range c.Raw {
    57  		if f.Name == ValuesfileName {
    58  			vf := filepath.Join(outdir, ValuesfileName)
    59  			if err := writeFile(vf, f.Data); err != nil {
    60  				return err
    61  			}
    62  		}
    63  	}
    64  
    65  	// Save values.schema.json if it exists
    66  	if c.Schema != nil {
    67  		filename := filepath.Join(outdir, SchemafileName)
    68  		if err := writeFile(filename, c.Schema); err != nil {
    69  			return err
    70  		}
    71  	}
    72  
    73  	// Save templates and files
    74  	for _, o := range [][]*chart.File{c.Templates, c.Files} {
    75  		for _, f := range o {
    76  			n := filepath.Join(outdir, f.Name)
    77  			if err := writeFile(n, f.Data); err != nil {
    78  				return err
    79  			}
    80  		}
    81  	}
    82  
    83  	// Save dependencies
    84  	base := filepath.Join(outdir, ChartsDir)
    85  	for _, dep := range c.Dependencies() {
    86  		// Here, we write each dependency as a tar file.
    87  		if _, err := Save(dep, base); err != nil {
    88  			return errors.Wrapf(err, "saving %s", dep.ChartFullPath())
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  // Save creates an archived chart to the given directory.
    95  //
    96  // This takes an existing chart and a destination directory.
    97  //
    98  // If the directory is /foo, and the chart is named bar, with version 1.0.0, this
    99  // will generate /foo/bar-1.0.0.tgz.
   100  //
   101  // This returns the absolute path to the chart archive file.
   102  func Save(c *chart.Chart, outDir string) (string, error) {
   103  	if err := c.Validate(); err != nil {
   104  		return "", errors.Wrap(err, "chart validation")
   105  	}
   106  
   107  	filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version)
   108  	filename = filepath.Join(outDir, filename)
   109  	dir := filepath.Dir(filename)
   110  	if stat, err := os.Stat(dir); err != nil {
   111  		if os.IsNotExist(err) {
   112  			if err2 := os.MkdirAll(dir, 0755); err2 != nil {
   113  				return "", err2
   114  			}
   115  		} else {
   116  			return "", errors.Wrapf(err, "stat %s", dir)
   117  		}
   118  	} else if !stat.IsDir() {
   119  		return "", errors.Errorf("is not a directory: %s", dir)
   120  	}
   121  
   122  	f, err := os.Create(filename)
   123  	if err != nil {
   124  		return "", err
   125  	}
   126  
   127  	// Wrap in gzip writer
   128  	zipper := gzip.NewWriter(f)
   129  	zipper.Header.Extra = headerBytes
   130  	zipper.Header.Comment = "Helm"
   131  
   132  	// Wrap in tar writer
   133  	twriter := tar.NewWriter(zipper)
   134  	rollback := false
   135  	defer func() {
   136  		twriter.Close()
   137  		zipper.Close()
   138  		f.Close()
   139  		if rollback {
   140  			os.Remove(filename)
   141  		}
   142  	}()
   143  
   144  	if err := writeTarContents(twriter, c, ""); err != nil {
   145  		rollback = true
   146  		return filename, err
   147  	}
   148  	return filename, nil
   149  }
   150  
   151  func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
   152  	base := filepath.Join(prefix, c.Name())
   153  
   154  	// Pull out the dependencies of a v1 Chart, since there's no way
   155  	// to tell the serializer to skip a field for just this use case
   156  	savedDependencies := c.Metadata.Dependencies
   157  	if c.Metadata.APIVersion == chart.APIVersionV1 {
   158  		c.Metadata.Dependencies = nil
   159  	}
   160  	// Save Chart.yaml
   161  	cdata, err := yaml.Marshal(c.Metadata)
   162  	if c.Metadata.APIVersion == chart.APIVersionV1 {
   163  		c.Metadata.Dependencies = savedDependencies
   164  	}
   165  	if err != nil {
   166  		return err
   167  	}
   168  	if err := writeToTar(out, filepath.Join(base, ChartfileName), cdata); err != nil {
   169  		return err
   170  	}
   171  
   172  	// Save Chart.lock
   173  	// TODO: remove the APIVersion check when APIVersionV1 is not used anymore
   174  	if c.Metadata.APIVersion == chart.APIVersionV2 {
   175  		if c.Lock != nil {
   176  			ldata, err := yaml.Marshal(c.Lock)
   177  			if err != nil {
   178  				return err
   179  			}
   180  			if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata); err != nil {
   181  				return err
   182  			}
   183  		}
   184  	}
   185  
   186  	// Save values.yaml
   187  	for _, f := range c.Raw {
   188  		if f.Name == ValuesfileName {
   189  			if err := writeToTar(out, filepath.Join(base, ValuesfileName), f.Data); err != nil {
   190  				return err
   191  			}
   192  		}
   193  	}
   194  
   195  	// Save values.schema.json if it exists
   196  	if c.Schema != nil {
   197  		if !json.Valid(c.Schema) {
   198  			return errors.New("Invalid JSON in " + SchemafileName)
   199  		}
   200  		if err := writeToTar(out, filepath.Join(base, SchemafileName), c.Schema); err != nil {
   201  			return err
   202  		}
   203  	}
   204  
   205  	// Save templates
   206  	for _, f := range c.Templates {
   207  		n := filepath.Join(base, f.Name)
   208  		if err := writeToTar(out, n, f.Data); err != nil {
   209  			return err
   210  		}
   211  	}
   212  
   213  	// Save files
   214  	for _, f := range c.Files {
   215  		n := filepath.Join(base, f.Name)
   216  		if err := writeToTar(out, n, f.Data); err != nil {
   217  			return err
   218  		}
   219  	}
   220  
   221  	// Save dependencies
   222  	for _, dep := range c.Dependencies() {
   223  		if err := writeTarContents(out, dep, filepath.Join(base, ChartsDir)); err != nil {
   224  			return err
   225  		}
   226  	}
   227  	return nil
   228  }
   229  
   230  // writeToTar writes a single file to a tar archive.
   231  func writeToTar(out *tar.Writer, name string, body []byte) error {
   232  	// TODO: Do we need to create dummy parent directory names if none exist?
   233  	h := &tar.Header{
   234  		Name:    filepath.ToSlash(name),
   235  		Mode:    0644,
   236  		Size:    int64(len(body)),
   237  		ModTime: time.Now(),
   238  	}
   239  	if err := out.WriteHeader(h); err != nil {
   240  		return err
   241  	}
   242  	_, err := out.Write(body)
   243  	return err
   244  }