1package zipartifacts 2 3import ( 4 "compress/gzip" 5 "encoding/binary" 6 "encoding/json" 7 "io" 8 "path" 9 "sort" 10 "strconv" 11 12 zip "gitlab.com/gitlab-org/golang-archive-zip" 13) 14 15type metadata struct { 16 Modified int64 `json:"modified,omitempty"` 17 Mode string `json:"mode,omitempty"` 18 CRC uint32 `json:"crc,omitempty"` 19 Size uint64 `json:"size,omitempty"` 20 Zipped uint64 `json:"zipped,omitempty"` 21 Comment string `json:"comment,omitempty"` 22} 23 24const MetadataHeaderPrefix = "\x00\x00\x00&" // length of string below, encoded properly 25const MetadataHeader = "GitLab Build Artifacts Metadata 0.0.2\n" 26 27func newMetadata(file *zip.File) metadata { 28 if file == nil { 29 return metadata{} 30 } 31 32 return metadata{ 33 //lint:ignore SA1019 Remove this once the minimum supported version is go 1.10 (go 1.9 and down do not support an alternative) 34 Modified: file.ModTime().Unix(), 35 Mode: strconv.FormatUint(uint64(file.Mode().Perm()), 8), 36 CRC: file.CRC32, 37 Size: file.UncompressedSize64, 38 Zipped: file.CompressedSize64, 39 Comment: file.Comment, 40 } 41} 42 43func (m metadata) writeEncoded(output io.Writer) error { 44 j, err := json.Marshal(m) 45 if err != nil { 46 return err 47 } 48 j = append(j, byte('\n')) 49 return writeBytes(output, j) 50} 51 52func writeZipEntryMetadata(output io.Writer, path string, entry *zip.File) error { 53 if err := writeString(output, path); err != nil { 54 return err 55 } 56 57 if err := newMetadata(entry).writeEncoded(output); err != nil { 58 return err 59 } 60 61 return nil 62} 63 64func GenerateZipMetadata(w io.Writer, archive *zip.Reader) error { 65 output := gzip.NewWriter(w) 66 defer output.Close() 67 68 if err := writeString(output, MetadataHeader); err != nil { 69 return err 70 } 71 72 // Write empty error header that we may need in the future 73 if err := writeString(output, "{}"); err != nil { 74 return err 75 } 76 77 // Create map of files in zip archive 78 zipMap := make(map[string]*zip.File, len(archive.File)) 79 80 // Add missing entries 81 for _, entry := range archive.File { 82 zipMap[entry.Name] = entry 83 84 for d := path.Dir(entry.Name); d != "." && d != "/"; d = path.Dir(d) { 85 entryDir := d + "/" 86 if _, ok := zipMap[entryDir]; !ok { 87 zipMap[entryDir] = nil 88 } 89 } 90 } 91 92 // Sort paths 93 sortedPaths := make([]string, 0, len(zipMap)) 94 for path := range zipMap { 95 sortedPaths = append(sortedPaths, path) 96 } 97 sort.Strings(sortedPaths) 98 99 // Write all files 100 for _, path := range sortedPaths { 101 if err := writeZipEntryMetadata(output, path, zipMap[path]); err != nil { 102 return err 103 } 104 } 105 return nil 106} 107 108func writeBytes(output io.Writer, data []byte) error { 109 err := binary.Write(output, binary.BigEndian, uint32(len(data))) 110 if err == nil { 111 _, err = output.Write(data) 112 } 113 return err 114} 115 116func writeString(output io.Writer, str string) error { 117 return writeBytes(output, []byte(str)) 118} 119