Skip to content

Commit f852f72

Browse files
Use writediff
1 parent f25863b commit f852f72

File tree

2 files changed

+45
-89
lines changed

2 files changed

+45
-89
lines changed

cmd/nerdctl/container/container_export_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ import (
2323
"path/filepath"
2424
"testing"
2525

26+
"gotest.tools/v3/assert"
27+
2628
"github.com/containerd/nerdctl/mod/tigron/test"
2729
"github.com/containerd/nerdctl/mod/tigron/tig"
30+
2831
"github.com/containerd/nerdctl/v2/pkg/testutil"
2932
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
30-
"gotest.tools/v3/assert"
3133
)
3234

3335
// validateExportedTar checks that the tar file exists and contains /bin/busybox

pkg/cmd/container/export.go

Lines changed: 42 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@ package container
1919
import (
2020
"context"
2121
"fmt"
22-
"io"
2322
"os"
2423

2524
containerd "github.com/containerd/containerd/v2/client"
26-
"github.com/containerd/containerd/v2/core/containers"
2725
"github.com/containerd/containerd/v2/core/mount"
2826
"github.com/containerd/containerd/v2/pkg/archive"
2927
"github.com/containerd/log"
@@ -54,61 +52,45 @@ func Export(ctx context.Context, client *containerd.Client, containerReq string,
5452
}
5553

5654
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)
6157
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)
8659
}
8760

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())
9465
if err != nil {
95-
return "", 0, err
66+
return fmt.Errorf("failed to get container mounts: %w", err)
9667
}
9768

98-
status, err := task.Status(ctx)
69+
// Create a temporary directory to mount the snapshot
70+
tempDir, err := os.MkdirTemp("", "nerdctl-export-")
9971
if err != nil {
100-
return "", 0, err
72+
return fmt.Errorf("failed to create temporary mount directory: %w", err)
10173
}
74+
defer os.RemoveAll(tempDir)
10275

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)
10580
}
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)
10688

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)
10991
}
11092

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 {
11294
// Create a temporary empty directory to use as the "before" state for WriteDiff
11395
emptyDir, err := os.MkdirTemp("", "nerdctl-export-empty-")
11496
if err != nil {
@@ -136,57 +118,29 @@ func createTarArchive(ctx context.Context, rootPath string, pid int, options typ
136118
}
137119
}
138120

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+
}
141135

142136
// Use WriteDiff to create a tar stream comparing the container rootfs (rootPath)
143137
// 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)
145139
if err != nil {
146140
return fmt.Errorf("failed to write tar diff: %w", err)
147141
}
148142

149-
log.G(ctx).Debugf("WriteDiff completed successfully, wrote %d bytes", cw.count)
143+
log.G(ctx).Debugf("WriteDiff completed successfully")
150144

151145
return nil
152146
}
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

Comments
 (0)