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 }