istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/docker-builder/builder/tar.go (about) 1 // Copyright Istio Authors 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 builder 16 17 import ( 18 "archive/tar" 19 "io" 20 "io/fs" 21 "os" 22 "path/filepath" 23 "time" 24 ) 25 26 func WriteArchiveFromFiles(base string, files map[string]string, out io.Writer) error { 27 tw := tar.NewWriter(out) 28 defer tw.Close() 29 30 for dest, srcRel := range files { 31 src := srcRel 32 if !filepath.IsAbs(src) { 33 src = filepath.Join(base, srcRel) 34 } 35 i, err := os.Stat(src) 36 if err != nil { 37 return err 38 } 39 isDir := i.IsDir() 40 ts := src 41 write := func(src string) error { 42 rel, _ := filepath.Rel(ts, src) 43 info, err := os.Stat(src) 44 if err != nil { 45 return err 46 } 47 48 var link string 49 if info.Mode()&os.ModeSymlink == os.ModeSymlink { 50 // fs.FS does not implement readlink, so we have this hack for now. 51 if link, err = os.Readlink(src); err != nil { 52 return err 53 } 54 } 55 56 header, err := tar.FileInfoHeader(info, link) 57 if err != nil { 58 return err 59 } 60 // work around some weirdness, without this we wind up with just the basename 61 header.Name = dest 62 if isDir { 63 header.Name = filepath.Join(dest, rel) 64 } 65 66 if IsExecOwner(info.Mode()) { 67 header.Mode = 0o755 68 } else { 69 header.Mode = 0o644 70 } 71 header.Uid = 0 72 header.Gid = 0 73 74 // TODO: if we want reproducible builds we can fake the timestamps here 75 76 if err := tw.WriteHeader(header); err != nil { 77 return err 78 } 79 80 if info.Mode().IsRegular() { 81 data, err := os.Open(src) 82 if err != nil { 83 return err 84 } 85 86 defer data.Close() 87 88 if _, err := io.Copy(tw, data); err != nil { 89 return err 90 } 91 } 92 return nil 93 } 94 95 if isDir { 96 if err := filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error { 97 if err != nil { 98 return err 99 } 100 return write(path) 101 }); err != nil { 102 return err 103 } 104 } else { 105 if err := write(src); err != nil { 106 return err 107 } 108 } 109 } 110 111 return nil 112 } 113 114 var WriteTime = time.Time{} 115 116 // Writes a raw TAR archive to out, given an fs.FS. 117 func WriteArchiveFromFS(base string, fsys fs.FS, out io.Writer, sourceDateEpoch time.Time) error { 118 tw := tar.NewWriter(out) 119 defer tw.Close() 120 121 if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { 122 if err != nil { 123 return err 124 } 125 126 info, err := d.Info() 127 if err != nil { 128 return err 129 } 130 131 if info.Mode()&os.ModeSymlink == os.ModeSymlink { 132 var link string 133 // fs.FS does not implement readlink, so we have this hack for now. 134 if link, err = os.Readlink(filepath.Join(base, path)); err != nil { 135 return err 136 } 137 // Resolve the link 138 if info, err = os.Stat(link); err != nil { 139 return err 140 } 141 } 142 143 header, err := tar.FileInfoHeader(info, "") 144 if err != nil { 145 return err 146 } 147 // work around some weirdness, without this we wind up with just the basename 148 header.Name = path 149 150 if err := tw.WriteHeader(header); err != nil { 151 return err 152 } 153 154 if info.Mode().IsRegular() { 155 data, err := fsys.Open(path) 156 if err != nil { 157 return err 158 } 159 160 defer data.Close() 161 162 if _, err := io.Copy(tw, data); err != nil { 163 return err 164 } 165 } 166 167 return nil 168 }); err != nil { 169 return err 170 } 171 172 return nil 173 } 174 175 func IsExecOwner(mode os.FileMode) bool { 176 return mode&0o100 != 0 177 }