diff options
Diffstat (limited to 'workhorse/internal/zipartifacts/metadata.go')
-rw-r--r-- | workhorse/internal/zipartifacts/metadata.go | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/workhorse/internal/zipartifacts/metadata.go b/workhorse/internal/zipartifacts/metadata.go new file mode 100644 index 00000000000..1ecf52deafb --- /dev/null +++ b/workhorse/internal/zipartifacts/metadata.go @@ -0,0 +1,117 @@ +package zipartifacts + +import ( + "archive/zip" + "compress/gzip" + "encoding/binary" + "encoding/json" + "io" + "path" + "sort" + "strconv" +) + +type metadata struct { + Modified int64 `json:"modified,omitempty"` + Mode string `json:"mode,omitempty"` + CRC uint32 `json:"crc,omitempty"` + Size uint64 `json:"size,omitempty"` + Zipped uint64 `json:"zipped,omitempty"` + Comment string `json:"comment,omitempty"` +} + +const MetadataHeaderPrefix = "\x00\x00\x00&" // length of string below, encoded properly +const MetadataHeader = "GitLab Build Artifacts Metadata 0.0.2\n" + +func newMetadata(file *zip.File) metadata { + if file == nil { + return metadata{} + } + + return metadata{ + //lint:ignore SA1019 Remove this once the minimum supported version is go 1.10 (go 1.9 and down do not support an alternative) + Modified: file.ModTime().Unix(), + Mode: strconv.FormatUint(uint64(file.Mode().Perm()), 8), + CRC: file.CRC32, + Size: file.UncompressedSize64, + Zipped: file.CompressedSize64, + Comment: file.Comment, + } +} + +func (m metadata) writeEncoded(output io.Writer) error { + j, err := json.Marshal(m) + if err != nil { + return err + } + j = append(j, byte('\n')) + return writeBytes(output, j) +} + +func writeZipEntryMetadata(output io.Writer, path string, entry *zip.File) error { + if err := writeString(output, path); err != nil { + return err + } + + if err := newMetadata(entry).writeEncoded(output); err != nil { + return err + } + + return nil +} + +func GenerateZipMetadata(w io.Writer, archive *zip.Reader) error { + output := gzip.NewWriter(w) + defer output.Close() + + if err := writeString(output, MetadataHeader); err != nil { + return err + } + + // Write empty error header that we may need in the future + if err := writeString(output, "{}"); err != nil { + return err + } + + // Create map of files in zip archive + zipMap := make(map[string]*zip.File, len(archive.File)) + + // Add missing entries + for _, entry := range archive.File { + zipMap[entry.Name] = entry + + for d := path.Dir(entry.Name); d != "." && d != "/"; d = path.Dir(d) { + entryDir := d + "/" + if _, ok := zipMap[entryDir]; !ok { + zipMap[entryDir] = nil + } + } + } + + // Sort paths + sortedPaths := make([]string, 0, len(zipMap)) + for path := range zipMap { + sortedPaths = append(sortedPaths, path) + } + sort.Strings(sortedPaths) + + // Write all files + for _, path := range sortedPaths { + if err := writeZipEntryMetadata(output, path, zipMap[path]); err != nil { + return err + } + } + return nil +} + +func writeBytes(output io.Writer, data []byte) error { + err := binary.Write(output, binary.BigEndian, uint32(len(data))) + if err == nil { + _, err = output.Write(data) + } + return err +} + +func writeString(output io.Writer, str string) error { + return writeBytes(output, []byte(str)) +} |