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