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