github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/chartutil/save_test.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  	"bytes"
    22  	"compress/gzip"
    23  	"io"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/stefanmcshane/helm/internal/test/ensure"
    33  	"github.com/stefanmcshane/helm/pkg/chart"
    34  	"github.com/stefanmcshane/helm/pkg/chart/loader"
    35  )
    36  
    37  func TestSave(t *testing.T) {
    38  	tmp := ensure.TempDir(t)
    39  	defer os.RemoveAll(tmp)
    40  
    41  	for _, dest := range []string{tmp, path.Join(tmp, "newdir")} {
    42  		t.Run("outDir="+dest, func(t *testing.T) {
    43  			c := &chart.Chart{
    44  				Metadata: &chart.Metadata{
    45  					APIVersion: chart.APIVersionV1,
    46  					Name:       "ahab",
    47  					Version:    "1.2.3",
    48  				},
    49  				Lock: &chart.Lock{
    50  					Digest: "testdigest",
    51  				},
    52  				Files: []*chart.File{
    53  					{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
    54  				},
    55  				Schema: []byte("{\n  \"title\": \"Values\"\n}"),
    56  			}
    57  			chartWithInvalidJSON := withSchema(*c, []byte("{"))
    58  
    59  			where, err := Save(c, dest)
    60  			if err != nil {
    61  				t.Fatalf("Failed to save: %s", err)
    62  			}
    63  			if !strings.HasPrefix(where, dest) {
    64  				t.Fatalf("Expected %q to start with %q", where, dest)
    65  			}
    66  			if !strings.HasSuffix(where, ".tgz") {
    67  				t.Fatalf("Expected %q to end with .tgz", where)
    68  			}
    69  
    70  			c2, err := loader.LoadFile(where)
    71  			if err != nil {
    72  				t.Fatal(err)
    73  			}
    74  			if c2.Name() != c.Name() {
    75  				t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name())
    76  			}
    77  			if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" {
    78  				t.Fatal("Files data did not match")
    79  			}
    80  			if c2.Lock != nil {
    81  				t.Fatal("Expected v1 chart archive not to contain Chart.lock file")
    82  			}
    83  
    84  			if !bytes.Equal(c.Schema, c2.Schema) {
    85  				indentation := 4
    86  				formattedExpected := Indent(indentation, string(c.Schema))
    87  				formattedActual := Indent(indentation, string(c2.Schema))
    88  				t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual)
    89  			}
    90  			if _, err := Save(&chartWithInvalidJSON, dest); err == nil {
    91  				t.Fatalf("Invalid JSON was not caught while saving chart")
    92  			}
    93  
    94  			c.Metadata.APIVersion = chart.APIVersionV2
    95  			where, err = Save(c, dest)
    96  			if err != nil {
    97  				t.Fatalf("Failed to save: %s", err)
    98  			}
    99  			c2, err = loader.LoadFile(where)
   100  			if err != nil {
   101  				t.Fatal(err)
   102  			}
   103  			if c2.Lock == nil {
   104  				t.Fatal("Expected v2 chart archive to contain a Chart.lock file")
   105  			}
   106  			if c2.Lock.Digest != c.Lock.Digest {
   107  				t.Fatal("Chart.lock data did not match")
   108  			}
   109  		})
   110  	}
   111  }
   112  
   113  // Creates a copy with a different schema; does not modify anything.
   114  func withSchema(chart chart.Chart, schema []byte) chart.Chart {
   115  	chart.Schema = schema
   116  	return chart
   117  }
   118  
   119  func Indent(n int, text string) string {
   120  	startOfLine := regexp.MustCompile(`(?m)^`)
   121  	indentation := strings.Repeat(" ", n)
   122  	return startOfLine.ReplaceAllLiteralString(text, indentation)
   123  }
   124  
   125  func TestSavePreservesTimestamps(t *testing.T) {
   126  	// Test executes so quickly that if we don't subtract a second, the
   127  	// check will fail because `initialCreateTime` will be identical to the
   128  	// written timestamp for the files.
   129  	initialCreateTime := time.Now().Add(-1 * time.Second)
   130  
   131  	tmp := t.TempDir()
   132  
   133  	c := &chart.Chart{
   134  		Metadata: &chart.Metadata{
   135  			APIVersion: chart.APIVersionV1,
   136  			Name:       "ahab",
   137  			Version:    "1.2.3",
   138  		},
   139  		Values: map[string]interface{}{
   140  			"imageName": "testimage",
   141  			"imageId":   42,
   142  		},
   143  		Files: []*chart.File{
   144  			{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
   145  		},
   146  		Schema: []byte("{\n  \"title\": \"Values\"\n}"),
   147  	}
   148  
   149  	where, err := Save(c, tmp)
   150  	if err != nil {
   151  		t.Fatalf("Failed to save: %s", err)
   152  	}
   153  
   154  	allHeaders, err := retrieveAllHeadersFromTar(where)
   155  	if err != nil {
   156  		t.Fatalf("Failed to parse tar: %v", err)
   157  	}
   158  
   159  	for _, header := range allHeaders {
   160  		if header.ModTime.Before(initialCreateTime) {
   161  			t.Fatalf("File timestamp not preserved: %v", header.ModTime)
   162  		}
   163  	}
   164  }
   165  
   166  // We could refactor `load.go` to use this `retrieveAllHeadersFromTar` function
   167  // as well, so we are not duplicating components of the code which iterate
   168  // through the tar.
   169  func retrieveAllHeadersFromTar(path string) ([]*tar.Header, error) {
   170  	raw, err := os.Open(path)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	defer raw.Close()
   175  
   176  	unzipped, err := gzip.NewReader(raw)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	defer unzipped.Close()
   181  
   182  	tr := tar.NewReader(unzipped)
   183  	headers := []*tar.Header{}
   184  	for {
   185  		hd, err := tr.Next()
   186  		if err == io.EOF {
   187  			break
   188  		}
   189  
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  
   194  		headers = append(headers, hd)
   195  	}
   196  
   197  	return headers, nil
   198  }
   199  
   200  func TestSaveDir(t *testing.T) {
   201  	tmp := t.TempDir()
   202  
   203  	c := &chart.Chart{
   204  		Metadata: &chart.Metadata{
   205  			APIVersion: chart.APIVersionV1,
   206  			Name:       "ahab",
   207  			Version:    "1.2.3",
   208  		},
   209  		Files: []*chart.File{
   210  			{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
   211  		},
   212  		Templates: []*chart.File{
   213  			{Name: filepath.Join(TemplatesDir, "nested", "dir", "thing.yaml"), Data: []byte("abc: {{ .Values.abc }}")},
   214  		},
   215  	}
   216  
   217  	if err := SaveDir(c, tmp); err != nil {
   218  		t.Fatalf("Failed to save: %s", err)
   219  	}
   220  
   221  	c2, err := loader.LoadDir(tmp + "/ahab")
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  
   226  	if c2.Name() != c.Name() {
   227  		t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name())
   228  	}
   229  
   230  	if len(c2.Templates) != 1 || c2.Templates[0].Name != filepath.Join(TemplatesDir, "nested", "dir", "thing.yaml") {
   231  		t.Fatal("Templates data did not match")
   232  	}
   233  
   234  	if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" {
   235  		t.Fatal("Files data did not match")
   236  	}
   237  }