@@ -19,11 +19,9 @@ package container
19
19
import (
20
20
"context"
21
21
"fmt"
22
- "io"
23
22
"os"
24
23
25
24
containerd "github.com/containerd/containerd/v2/client"
26
- "github.com/containerd/containerd/v2/core/containers"
27
25
"github.com/containerd/containerd/v2/core/mount"
28
26
"github.com/containerd/containerd/v2/pkg/archive"
29
27
"github.com/containerd/log"
@@ -54,61 +52,45 @@ func Export(ctx context.Context, client *containerd.Client, containerReq string,
54
52
}
55
53
56
54
func exportContainer (ctx context.Context , client * containerd.Client , container containerd.Container , options types.ContainerExportOptions ) error {
57
- // Try to get a running container root first
58
- root , pid , err := getContainerRoot (ctx , container )
59
- var cleanup func () error
60
-
55
+ // Get container info to access the snapshot
56
+ conInfo , err := container .Info (ctx )
61
57
if err != nil {
62
- // Container is not running, try to mount the snapshot
63
- var conInfo containers.Container
64
- conInfo , err = container .Info (ctx )
65
- if err != nil {
66
- return fmt .Errorf ("failed to get container info: %w" , err )
67
- }
68
-
69
- root , cleanup , err = MountSnapshotForContainer (ctx , client , conInfo , options .GOptions .Snapshotter )
70
- if cleanup != nil {
71
- defer func () {
72
- if cleanupErr := cleanup (); cleanupErr != nil {
73
- log .G (ctx ).WithError (cleanupErr ).Warn ("Failed to cleanup mounted snapshot" )
74
- }
75
- }()
76
- }
77
-
78
- if err != nil {
79
- return fmt .Errorf ("failed to mount container snapshot: %w" , err )
80
- }
81
- log .G (ctx ).Debugf ("Mounted snapshot at %s" , root )
82
- // For stopped containers, set pid to 0 to avoid nsenter
83
- pid = 0
84
- } else {
85
- log .G (ctx ).Debugf ("Using running container root %s (pid %d)" , root , pid )
58
+ return fmt .Errorf ("failed to get container info: %w" , err )
86
59
}
87
60
88
- // Create tar command to export the rootfs
89
- return createTarArchive (ctx , root , pid , options )
90
- }
91
-
92
- func getContainerRoot (ctx context.Context , container containerd.Container ) (string , int , error ) {
93
- task , err := container .Task (ctx , nil )
61
+ // Use the container's snapshot service to get mounts
62
+ // This works for both running and stopped containers
63
+ sn := client .SnapshotService (conInfo .Snapshotter )
64
+ mounts , err := sn .Mounts (ctx , container .ID ())
94
65
if err != nil {
95
- return " " , 0 , err
66
+ return fmt . Errorf ( "failed to get container mounts: %w " , err )
96
67
}
97
68
98
- status , err := task .Status (ctx )
69
+ // Create a temporary directory to mount the snapshot
70
+ tempDir , err := os .MkdirTemp ("" , "nerdctl-export-" )
99
71
if err != nil {
100
- return " " , 0 , err
72
+ return fmt . Errorf ( "failed to create temporary mount directory: %w " , err )
101
73
}
74
+ defer os .RemoveAll (tempDir )
102
75
103
- if status .Status != containerd .Running {
104
- return "" , 0 , fmt .Errorf ("container is not running" )
76
+ // Mount the container's filesystem
77
+ err = mount .All (mounts , tempDir )
78
+ if err != nil {
79
+ return fmt .Errorf ("failed to mount container snapshot: %w" , err )
105
80
}
81
+ defer func () {
82
+ if unmountErr := mount .Unmount (tempDir , 0 ); unmountErr != nil {
83
+ log .G (ctx ).WithError (unmountErr ).Warn ("Failed to unmount snapshot" )
84
+ }
85
+ }()
86
+
87
+ log .G (ctx ).Debugf ("Mounted container snapshot at %s" , tempDir )
106
88
107
- pid := int ( task . Pid ())
108
- return fmt . Sprintf ( "/proc/%d/root" , pid ), pid , nil
89
+ // Create tar archive using WriteDiff
90
+ return createTarArchiveWithWriteDiff ( ctx , tempDir , options )
109
91
}
110
92
111
- func createTarArchive (ctx context.Context , rootPath string , pid int , options types.ContainerExportOptions ) error {
93
+ func createTarArchiveWithWriteDiff (ctx context.Context , rootPath string , options types.ContainerExportOptions ) error {
112
94
// Create a temporary empty directory to use as the "before" state for WriteDiff
113
95
emptyDir , err := os .MkdirTemp ("" , "nerdctl-export-empty-" )
114
96
if err != nil {
@@ -136,57 +118,29 @@ func createTarArchive(ctx context.Context, rootPath string, pid int, options typ
136
118
}
137
119
}
138
120
139
- // Create a counting writer to track bytes written
140
- cw := & countingWriter {w : options .Stdout }
121
+ // Double check that emptyDir is empty
122
+ if entries , err := os .ReadDir (emptyDir ); err != nil {
123
+ log .G (ctx ).Debugf ("Failed to read emptyDir directory %s: %v" , emptyDir , err )
124
+ } else {
125
+ log .G (ctx ).Debugf ("EmptyDir %s contains %d entries" , emptyDir , len (entries ))
126
+ for i , entry := range entries {
127
+ if i < 10 { // Only log first 10 entries to avoid spam
128
+ log .G (ctx ).Debugf (" - %s (dir: %v)" , entry .Name (), entry .IsDir ())
129
+ }
130
+ }
131
+ if len (entries ) > 10 {
132
+ log .G (ctx ).Debugf (" ... and %d more entries" , len (entries )- 10 )
133
+ }
134
+ }
141
135
142
136
// Use WriteDiff to create a tar stream comparing the container rootfs (rootPath)
143
137
// with an empty directory (emptyDir). This produces a complete export of the container.
144
- err = archive .WriteDiff (ctx , cw , rootPath , emptyDir )
138
+ err = archive .WriteDiff (ctx , options . Stdout , emptyDir , rootPath )
145
139
if err != nil {
146
140
return fmt .Errorf ("failed to write tar diff: %w" , err )
147
141
}
148
142
149
- log .G (ctx ).Debugf ("WriteDiff completed successfully, wrote %d bytes" , cw . count )
143
+ log .G (ctx ).Debugf ("WriteDiff completed successfully" )
150
144
151
145
return nil
152
146
}
153
-
154
- // countingWriter wraps an io.Writer and counts the bytes written
155
- type countingWriter struct {
156
- w io.Writer
157
- count int64
158
- }
159
-
160
- func (cw * countingWriter ) Write (p []byte ) (n int , err error ) {
161
- n , err = cw .w .Write (p )
162
- cw .count += int64 (n )
163
- return n , err
164
- }
165
-
166
- func MountSnapshotForContainer (ctx context.Context , client * containerd.Client , conInfo containers.Container , snapshotter string ) (string , func () error , error ) {
167
- snapKey := conInfo .SnapshotKey
168
- resp , err := client .SnapshotService (snapshotter ).Mounts (ctx , snapKey )
169
- if err != nil {
170
- return "" , nil , err
171
- }
172
-
173
- tempDir , err := os .MkdirTemp ("" , "nerdctl-cp-" )
174
- if err != nil {
175
- return "" , nil , err
176
- }
177
-
178
- err = mount .All (resp , tempDir )
179
- if err != nil {
180
- return "" , nil , err
181
- }
182
-
183
- cleanup := func () error {
184
- err = mount .Unmount (tempDir , 0 )
185
- if err != nil {
186
- return err
187
- }
188
- return os .RemoveAll (tempDir )
189
- }
190
-
191
- return tempDir , cleanup , nil
192
- }
0 commit comments