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