github.com/migueleliasweb/helm@v2.6.1+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/facebookgo/symwalk" 32 "github.com/golang/protobuf/ptypes/any" 33 34 "k8s.io/helm/pkg/ignore" 35 "k8s.io/helm/pkg/proto/hapi/chart" 36 ) 37 38 // Load takes a string name, tries to resolve it to a file or directory, and then loads it. 39 // 40 // This is the preferred way to load a chart. It will discover the chart encoding 41 // and hand off to the appropriate chart reader. 42 // 43 // If a .helmignore file is present, the directory loader will skip loading any files 44 // matching it. But .helmignore is not evaluated when reading out of an archive. 45 func Load(name string) (*chart.Chart, error) { 46 fi, err := os.Stat(name) 47 if err != nil { 48 return nil, err 49 } 50 if fi.IsDir() { 51 if validChart, err := IsChartDir(name); !validChart { 52 return nil, err 53 } 54 return LoadDir(name) 55 } 56 return LoadFile(name) 57 } 58 59 // BufferedFile represents an archive file buffered for later processing. 60 type BufferedFile struct { 61 Name string 62 Data []byte 63 } 64 65 // LoadArchive loads from a reader containing a compressed tar archive. 66 func LoadArchive(in io.Reader) (*chart.Chart, error) { 67 unzipped, err := gzip.NewReader(in) 68 if err != nil { 69 return &chart.Chart{}, err 70 } 71 defer unzipped.Close() 72 73 files := []*BufferedFile{} 74 tr := tar.NewReader(unzipped) 75 for { 76 b := bytes.NewBuffer(nil) 77 hd, err := tr.Next() 78 if err == io.EOF { 79 break 80 } 81 if err != nil { 82 return &chart.Chart{}, err 83 } 84 85 if hd.FileInfo().IsDir() { 86 // Use this instead of hd.Typeflag because we don't have to do any 87 // inference chasing. 88 continue 89 } 90 91 // Archive could contain \ if generated on Windows 92 delimiter := "/" 93 if strings.ContainsRune(hd.Name, '\\') { 94 delimiter = "\\" 95 } 96 97 parts := strings.Split(hd.Name, delimiter) 98 n := strings.Join(parts[1:], delimiter) 99 100 // Normalize the path to the / delimiter 101 n = strings.Replace(n, delimiter, "/", -1) 102 103 if parts[0] == "Chart.yaml" { 104 return nil, errors.New("chart yaml not in base directory") 105 } 106 107 if _, err := io.Copy(b, tr); err != nil { 108 return &chart.Chart{}, err 109 } 110 111 files = append(files, &BufferedFile{Name: n, Data: b.Bytes()}) 112 b.Reset() 113 } 114 115 if len(files) == 0 { 116 return nil, errors.New("no files in chart archive") 117 } 118 119 return LoadFiles(files) 120 } 121 122 // LoadFiles loads from in-memory files. 123 func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { 124 c := &chart.Chart{} 125 subcharts := map[string][]*BufferedFile{} 126 127 for _, f := range files { 128 if f.Name == "Chart.yaml" { 129 m, err := UnmarshalChartfile(f.Data) 130 if err != nil { 131 return c, err 132 } 133 c.Metadata = m 134 } else if f.Name == "values.toml" { 135 return c, errors.New("values.toml is illegal as of 2.0.0-alpha.2") 136 } else if f.Name == "values.yaml" { 137 c.Values = &chart.Config{Raw: string(f.Data)} 138 } else if strings.HasPrefix(f.Name, "templates/") { 139 c.Templates = append(c.Templates, &chart.Template{Name: f.Name, Data: f.Data}) 140 } else if strings.HasPrefix(f.Name, "charts/") { 141 if filepath.Ext(f.Name) == ".prov" { 142 c.Files = append(c.Files, &any.Any{TypeUrl: f.Name, Value: f.Data}) 143 continue 144 } 145 cname := strings.TrimPrefix(f.Name, "charts/") 146 if strings.IndexAny(cname, "._") == 0 { 147 // Ignore charts/ that start with . or _. 148 continue 149 } 150 parts := strings.SplitN(cname, "/", 2) 151 scname := parts[0] 152 subcharts[scname] = append(subcharts[scname], &BufferedFile{Name: cname, Data: f.Data}) 153 } else { 154 c.Files = append(c.Files, &any.Any{TypeUrl: f.Name, Value: f.Data}) 155 } 156 } 157 158 // Ensure that we got a Chart.yaml file 159 if c.Metadata == nil { 160 return c, errors.New("chart metadata (Chart.yaml) missing") 161 } 162 if c.Metadata.Name == "" { 163 return c, errors.New("invalid chart (Chart.yaml): name must not be empty") 164 } 165 166 for n, files := range subcharts { 167 var sc *chart.Chart 168 var err error 169 if strings.IndexAny(n, "_.") == 0 { 170 continue 171 } else if filepath.Ext(n) == ".tgz" { 172 file := files[0] 173 if file.Name != n { 174 return c, fmt.Errorf("error unpacking tar in %s: expected %s, got %s", c.Metadata.Name, n, file.Name) 175 } 176 // Untar the chart and add to c.Dependencies 177 b := bytes.NewBuffer(file.Data) 178 sc, err = LoadArchive(b) 179 } else { 180 // We have to trim the prefix off of every file, and ignore any file 181 // that is in charts/, but isn't actually a chart. 182 buff := make([]*BufferedFile, 0, len(files)) 183 for _, f := range files { 184 parts := strings.SplitN(f.Name, "/", 2) 185 if len(parts) < 2 { 186 continue 187 } 188 f.Name = parts[1] 189 buff = append(buff, f) 190 } 191 sc, err = LoadFiles(buff) 192 } 193 194 if err != nil { 195 return c, fmt.Errorf("error unpacking %s in %s: %s", n, c.Metadata.Name, err) 196 } 197 198 c.Dependencies = append(c.Dependencies, sc) 199 } 200 201 return c, nil 202 } 203 204 // LoadFile loads from an archive file. 205 func LoadFile(name string) (*chart.Chart, error) { 206 if fi, err := os.Stat(name); err != nil { 207 return nil, err 208 } else if fi.IsDir() { 209 return nil, errors.New("cannot load a directory") 210 } 211 212 raw, err := os.Open(name) 213 if err != nil { 214 return nil, err 215 } 216 defer raw.Close() 217 218 return LoadArchive(raw) 219 } 220 221 // LoadDir loads from a directory. 222 // 223 // This loads charts only from directories. 224 func LoadDir(dir string) (*chart.Chart, error) { 225 topdir, err := filepath.Abs(dir) 226 if err != nil { 227 return nil, err 228 } 229 230 // Just used for errors. 231 c := &chart.Chart{} 232 233 rules := ignore.Empty() 234 ifile := filepath.Join(topdir, ignore.HelmIgnore) 235 if _, err := os.Stat(ifile); err == nil { 236 r, err := ignore.ParseFile(ifile) 237 if err != nil { 238 return c, err 239 } 240 rules = r 241 } 242 rules.AddDefaults() 243 244 files := []*BufferedFile{} 245 topdir += string(filepath.Separator) 246 247 err = symwalk.Walk(topdir, func(name string, fi os.FileInfo, err error) error { 248 n := strings.TrimPrefix(name, topdir) 249 250 // Normalize to / since it will also work on Windows 251 n = filepath.ToSlash(n) 252 253 if err != nil { 254 return err 255 } 256 if fi.IsDir() { 257 // Directory-based ignore rules should involve skipping the entire 258 // contents of that directory. 259 if rules.Ignore(n, fi) { 260 return filepath.SkipDir 261 } 262 return nil 263 } 264 265 // If a .helmignore file matches, skip this file. 266 if rules.Ignore(n, fi) { 267 return nil 268 } 269 270 data, err := ioutil.ReadFile(name) 271 if err != nil { 272 return fmt.Errorf("error reading %s: %s", n, err) 273 } 274 275 files = append(files, &BufferedFile{Name: n, Data: data}) 276 return nil 277 }) 278 if err != nil { 279 return c, err 280 } 281 282 return LoadFiles(files) 283 }