// zipInputFiles deterministically builds a zip archive out of input files and // writes it to the writer. Files are written in the order given. func zipInputFiles(files []File, w io.Writer, log logging.Logger) error { writer := zip.NewWriter(w) defer writer.Close() // Reports zipping progress to the log each second. lastReport := time.Time{} progress := func(count int) { if time.Since(lastReport) > time.Second { lastReport = time.Now() log.Infof("Zipping files: %d files left", len(files)-count) } } for i, in := range files { progress(i) // Intentionally do not add timestamp or file mode to make zip archive // deterministic. See also zip.FileInfoHeader() implementation. fh := zip.FileHeader{ Name: in.Name(), Method: zip.Deflate, } mode := os.FileMode(0600) if in.Executable() { mode |= 0100 } if in.Symlink() { mode |= os.ModeSymlink } fh.SetMode(mode) dst, err := writer.CreateHeader(&fh) if err != nil { return err } if in.Symlink() { err = zipSymlinkFile(dst, in) } else { err = zipRegularFile(dst, in) } if err != nil { return err } } return nil }