Skip to content

Commit 94f6a2f

Browse files
[wanda] Wire context owner override through tar writing (#480)
Add an owner field to tarStream that, when set, overrides the uid/gid on all file entries written to the tar. Pass the owner through tarFile.writeTo so each file header gets the override applied. Topic: add-context-owner-tar Relative: add-context-owner Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: andrew <andrew@anyscale.com> --------- Signed-off-by: andrew <andrew@anyscale.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1a7ef66 commit 94f6a2f

4 files changed

Lines changed: 81 additions & 14 deletions

File tree

wanda/tar_file.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ type tarFile struct {
1919
meta *tarMeta
2020
}
2121

22-
func (t *tarFile) writeTo(tw *tar.Writer, modTime time.Time) error {
22+
func (t *tarFile) writeTo(
23+
tw *tar.Writer, modTime time.Time, owner *contextOwner,
24+
) error {
2325
stat, err := os.Lstat(t.srcFile)
2426
if err != nil {
2527
return fmt.Errorf("stat file %q: %w", t.srcFile, err)
@@ -37,6 +39,10 @@ func (t *tarFile) writeTo(tw *tar.Writer, modTime time.Time) error {
3739
Uid: meta.UserID,
3840
ModTime: modTime,
3941
}
42+
if owner != nil {
43+
hdr.Uid = owner.UserID
44+
hdr.Gid = owner.GroupID
45+
}
4046

4147
switch stat.Mode() & os.ModeType {
4248
case os.ModeSymlink:
@@ -94,7 +100,7 @@ type tarFileRecord struct {
94100
Symlink string `json:"symlink,omitempty"`
95101
}
96102

97-
func (t *tarFile) record() (*tarFileRecord, error) {
103+
func (t *tarFile) record(owner *contextOwner) (*tarFileRecord, error) {
98104
// Use Lstat to detect symlinks without following them.
99105
stat, err := os.Lstat(t.srcFile)
100106
if err != nil {
@@ -125,15 +131,20 @@ func (t *tarFile) record() (*tarFileRecord, error) {
125131
h.Write([]byte(target))
126132
contentDigest := sha256DigestString(h)
127133

128-
return &tarFileRecord{
134+
r := &tarFileRecord{
129135
Name: t.name,
130136
Mode: mode,
131137
GroupID: meta.GroupID,
132138
UserID: meta.UserID,
133139
Size: 0, // Symlinks have no content size in tar.
134140
ContentDigest: contentDigest,
135141
Symlink: target,
136-
}, nil
142+
}
143+
if owner != nil {
144+
r.UserID = owner.UserID
145+
r.GroupID = owner.GroupID
146+
}
147+
return r, nil
137148
default:
138149
f, err := os.Open(t.srcFile)
139150
if err != nil {
@@ -156,7 +167,10 @@ func (t *tarFile) record() (*tarFileRecord, error) {
156167

157168
ContentDigest: contentDigest,
158169
}
159-
170+
if owner != nil {
171+
r.UserID = owner.UserID
172+
r.GroupID = owner.GroupID
173+
}
160174
return r, nil
161175
}
162176
}

wanda/tar_file_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func TestTarFile(t *testing.T) {
3838
// Tarball only stores the mod time with second precision.
3939
modTime := time.Now().Truncate(time.Second)
4040

41-
if err := tf.writeTo(tw, modTime); err != nil {
41+
if err := tf.writeTo(tw, modTime, nil); err != nil {
4242
t.Fatalf("write to tar stream: %v", err)
4343
}
4444
if err := tw.Close(); err != nil {
@@ -100,7 +100,7 @@ func TestTarFileRecord(t *testing.T) {
100100
},
101101
}
102102

103-
r, err := tf.record()
103+
r, err := tf.record(nil)
104104
if err != nil {
105105
t.Fatalf("record: %v", err)
106106
}
@@ -142,7 +142,7 @@ func TestTarFileRecord(t *testing.T) {
142142
meta: tf.meta, // The same meta as the first file.
143143
}
144144

145-
r2, err := tf2.record()
145+
r2, err := tf2.record(nil)
146146
if err != nil {
147147
t.Fatalf("record for file 2: %v", err)
148148
}
@@ -185,7 +185,7 @@ func TestTarFileSymlink(t *testing.T) {
185185
tw := tar.NewWriter(tarBuf)
186186
modTime := time.Now().Truncate(time.Second)
187187

188-
if err := tf.writeTo(tw, modTime); err != nil {
188+
if err := tf.writeTo(tw, modTime, nil); err != nil {
189189
t.Fatalf("write symlink to tar stream: %v", err)
190190
}
191191
if err := tw.Close(); err != nil {
@@ -234,7 +234,7 @@ func TestTarFileSymlinkRecord(t *testing.T) {
234234
},
235235
}
236236

237-
r, err := tf.record()
237+
r, err := tf.record(nil)
238238
if err != nil {
239239
t.Fatalf("record: %v", err)
240240
}
@@ -277,7 +277,7 @@ func TestTarFileSymlinkRecord(t *testing.T) {
277277
meta: tf.meta,
278278
}
279279

280-
r2, err := tf2.record()
280+
r2, err := tf2.record(nil)
281281
if err != nil {
282282
t.Fatalf("record for link2: %v", err)
283283
}

wanda/tar_stream.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ type tarStream struct {
1818
// all files will use this mod time default. This makes the stream
1919
// deterministic and cachable.
2020
modTime time.Time
21+
22+
// owner overrides the uid/gid for all entries when set.
23+
owner *contextOwner
2124
}
2225

2326
// newTarStream creates a new tarball stream.
@@ -57,8 +60,10 @@ func (s *tarStream) writeTo(tw *tar.Writer) error {
5760

5861
for _, name := range names {
5962
f := s.files[name]
60-
if err := f.writeTo(tw, s.modTime); err != nil {
61-
return fmt.Errorf("write file %q to stream: %w", name, err)
63+
if err := f.writeTo(tw, s.modTime, s.owner); err != nil {
64+
return fmt.Errorf(
65+
"write file %q to stream: %w", name, err,
66+
)
6267
}
6368
}
6469
return nil
@@ -90,7 +95,7 @@ func (s *tarStream) digest() (string, error) {
9095
for _, name := range names {
9196
f := s.files[name]
9297

93-
r, err := f.record()
98+
r, err := f.record(s.owner)
9499
if err != nil {
95100
return "", fmt.Errorf("digest file %q: %w", name, err)
96101
}

wanda/tar_stream_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,54 @@ import (
99
"testing"
1010
)
1111

12+
func TestTarStreamContextOwner(t *testing.T) {
13+
tmp := t.TempDir()
14+
15+
f := filepath.Join(tmp, "testfile")
16+
if err := os.WriteFile(f, []byte("data"), 0644); err != nil {
17+
t.Fatalf("write file: %v", err)
18+
}
19+
20+
ts := newTarStream()
21+
ts.owner = &contextOwner{UserID: 2000, GroupID: 100}
22+
ts.addFile("a/b/file.txt", nil, f)
23+
24+
r := newWriterToReader(ts)
25+
buf := new(bytes.Buffer)
26+
if _, err := io.Copy(buf, r); err != nil {
27+
t.Fatalf("copy tar stream: %v", err)
28+
}
29+
30+
tr := tar.NewReader(buf)
31+
32+
hdr, err := tr.Next()
33+
if err != nil {
34+
t.Fatalf("read file header: %v", err)
35+
}
36+
if hdr.Name != "a/b/file.txt" {
37+
t.Errorf("got name %q, want %q", hdr.Name, "a/b/file.txt")
38+
}
39+
if hdr.Uid != 2000 {
40+
t.Errorf("got uid %d, want %d", hdr.Uid, 2000)
41+
}
42+
if hdr.Gid != 100 {
43+
t.Errorf("got gid %d, want %d", hdr.Gid, 100)
44+
}
45+
46+
d1, err := ts.digest()
47+
if err != nil {
48+
t.Fatalf("digest: %v", err)
49+
}
50+
ts.owner = &contextOwner{UserID: 3000, GroupID: 300}
51+
d2, err := ts.digest()
52+
if err != nil {
53+
t.Fatalf("digest: %v", err)
54+
}
55+
if d1 == d2 {
56+
t.Error("digest should change when owner is modified")
57+
}
58+
}
59+
1260
func TestTarStream(t *testing.T) {
1361
tmp := t.TempDir()
1462

0 commit comments

Comments
 (0)