github.com/jdhenke/godel@v0.0.0-20161213181855-abeb3861bf0d/cmd/godel/tgz.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package godel 16 17 import ( 18 "archive/tar" 19 "compress/gzip" 20 "io" 21 "os" 22 "path" 23 "sort" 24 "strings" 25 26 "github.com/pkg/errors" 27 28 "github.com/palantir/godel/layout" 29 ) 30 31 func verifyPackageTgz(tgzFile string) (string, error) { 32 entries, err := getPathsInTgz(tgzFile) 33 if err != nil { 34 return "", errors.Wrapf(err, "failed to get directories in tgz file %s", tgzFile) 35 } 36 actualPaths := sortedKeys(entries) 37 version, err := versionFromEntries(actualPaths) 38 if err != nil { 39 return "", errors.Wrapf(err, "could not determine version from tgz file entries") 40 } 41 42 expectedPaths := layout.AppSpec().Paths(layout.AppSpecTemplate(version), false) 43 for _, currExpectedPath := range expectedPaths { 44 if _, ok := entries[currExpectedPath]; !ok { 45 return "", errors.Errorf("tgz %s does not contain a valid package: failed to find %s.\nRequired: %v\nActual: %v", tgzFile, currExpectedPath, expectedPaths, actualPaths) 46 } 47 } 48 49 return version, nil 50 } 51 52 func versionFromEntries(sortedEntries []string) (string, error) { 53 dirEntry := sortedEntries[0] 54 expectedPrefix := layout.AppName + "-" 55 if !strings.HasPrefix(dirEntry, expectedPrefix) { 56 return "", errors.Errorf("entry %s in %v did not have expected prefix %s", dirEntry, sortedEntries, expectedPrefix) 57 } 58 return dirEntry[len(expectedPrefix):], nil 59 } 60 61 // getPathsInTgz returns a map that contains the paths to the entries present in the specified tgz file. The returned 62 // map will also contain subdirectories as independent entries -- that is, if the archive contains 63 // "root/intermediate/leaf.txt", the returned map will have "root", "root/intermediate" and "root/intermediate/leaf.txt" 64 // as entries. 65 func getPathsInTgz(tgzFile string) (rPaths map[string]bool, rErr error) { 66 file, err := os.Open(tgzFile) 67 if err != nil { 68 return nil, errors.Wrapf(err, "failed to open file %s", tgzFile) 69 } 70 defer func() { 71 if err := file.Close(); err != nil && rErr == nil { 72 rErr = errors.Wrapf(err, "failed to close file %s in defer", tgzFile) 73 } 74 }() 75 76 gzf, err := gzip.NewReader(file) 77 if err != nil { 78 return nil, errors.Wrapf(err, "failed to create gzip reader for file %s", tgzFile) 79 } 80 81 tarReader := tar.NewReader(gzf) 82 dirs := make(map[string]bool) 83 for { 84 header, err := tarReader.Next() 85 if err == io.EOF { 86 break 87 } else if err != nil { 88 return nil, errors.Wrapf(err, "failed to read entry in file %s", tgzFile) 89 } 90 91 switch header.Typeflag { 92 case tar.TypeDir: 93 dirs[path.Dir(header.Name)] = true 94 case tar.TypeReg: 95 dirs[header.Name] = true 96 default: 97 } 98 } 99 return dirs, nil 100 } 101 102 func sortedKeys(input map[string]bool) []string { 103 output := make([]string, 0, len(input)) 104 for key := range input { 105 output = append(output, key) 106 } 107 sort.Strings(output) 108 return output 109 }