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