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