Skip to content

Commit 98b30f4

Browse files
committed
Add commands to mount/unmount image from ref
Example: ```terminal $ mkdir /opt/busybox $ ctr image mount docker.io/library/busybox:latest /opt/busybox /opt/busybox $ ls -lh /opt/busybox total 40K drwxr-xr-x 2 root root 12K Apr 14 01:10 bin drwxr-xr-x 2 root root 4.0K Apr 14 01:10 dev drwxr-xr-x 3 root root 4.0K Apr 14 01:10 etc drwxr-xr-x 2 nobody nogroup 4.0K Apr 14 01:10 home drwx------ 2 root root 4.0K Apr 14 01:10 root drwxrwxrwt 2 root root 4.0K Apr 14 01:10 tmp drwxr-xr-x 3 root root 4.0K Apr 14 01:10 usr drwxr-xr-x 4 root root 4.0K Apr 14 01:10 var $ ctr image unmount /opt/busybox $ ls -lh /opt/busybox total 0 ``` Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent 36952e9 commit 98b30f4

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

cmd/ctr/commands/images/images.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ var Command = cli.Command{
4343
exportCommand,
4444
importCommand,
4545
listCommand,
46+
mountCommand,
47+
unmountCommand,
4648
pullCommand,
4749
pushCommand,
4850
removeCommand,

cmd/ctr/commands/images/mount.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package images
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/containerd/containerd"
8+
"github.com/containerd/containerd/cmd/ctr/commands"
9+
"github.com/containerd/containerd/errdefs"
10+
"github.com/containerd/containerd/leases"
11+
"github.com/containerd/containerd/mount"
12+
"github.com/containerd/containerd/platforms"
13+
"github.com/opencontainers/image-spec/identity"
14+
"github.com/pkg/errors"
15+
"github.com/urfave/cli"
16+
)
17+
18+
/*
19+
Copyright The containerd Authors.
20+
21+
Licensed under the Apache License, Version 2.0 (the "License");
22+
you may not use this file except in compliance with the License.
23+
You may obtain a copy of the License at
24+
25+
http://www.apache.org/licenses/LICENSE-2.0
26+
27+
Unless required by applicable law or agreed to in writing, software
28+
distributed under the License is distributed on an "AS IS" BASIS,
29+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30+
See the License for the specific language governing permissions and
31+
limitations under the License.
32+
*/
33+
34+
var mountCommand = cli.Command{
35+
Name: "mount",
36+
Usage: "mount an image to a target path",
37+
ArgsUsage: "[flags] <ref> <target>",
38+
Description: `Mount an image rootfs to a specified path.
39+
40+
When you are done, use the unmount command.
41+
`,
42+
Flags: append(append(commands.RegistryFlags, append(commands.SnapshotterFlags, commands.LabelFlag)...),
43+
cli.BoolFlag{
44+
Name: "rw",
45+
Usage: "Enable write support on the mount",
46+
},
47+
cli.StringFlag{
48+
Name: "platform",
49+
Usage: "Mount the image for the specified platform",
50+
Value: platforms.DefaultString(),
51+
},
52+
),
53+
Action: func(context *cli.Context) (retErr error) {
54+
var (
55+
ref = context.Args().First()
56+
target = context.Args().Get(1)
57+
)
58+
if ref == "" {
59+
return fmt.Errorf("please provide an image reference to mount")
60+
}
61+
if target == "" {
62+
return fmt.Errorf("please provide a target path to mount to")
63+
}
64+
65+
client, ctx, cancel, err := commands.NewClient(context)
66+
if err != nil {
67+
return err
68+
}
69+
defer cancel()
70+
71+
snapshotter := context.GlobalString("snapshotter")
72+
if snapshotter == "" {
73+
snapshotter = containerd.DefaultSnapshotter
74+
}
75+
76+
ctx, done, err := client.WithLease(ctx,
77+
leases.WithID(target),
78+
leases.WithExpiration(24*time.Hour),
79+
leases.WithLabels(map[string]string{
80+
"containerd.io/gc.ref.snapshot." + snapshotter: target,
81+
}),
82+
)
83+
if err != nil && !errdefs.IsAlreadyExists(err) {
84+
return err
85+
}
86+
87+
defer func() {
88+
if retErr != nil && done != nil {
89+
done(ctx)
90+
}
91+
}()
92+
93+
ps := context.String("platform")
94+
p, err := platforms.Parse(ps)
95+
if err != nil {
96+
return errors.Wrapf(err, "unable to parse platform %s", ps)
97+
}
98+
99+
img, err := client.ImageService().Get(ctx, ref)
100+
if err != nil {
101+
return err
102+
}
103+
104+
i := containerd.NewImageWithPlatform(client, img, platforms.Only(p))
105+
if err := i.Unpack(ctx, snapshotter); err != nil {
106+
return errors.Wrap(err, "error unpacking image")
107+
}
108+
109+
diffIDs, err := i.RootFS(ctx)
110+
if err != nil {
111+
return err
112+
}
113+
chainID := identity.ChainID(diffIDs).String()
114+
fmt.Println(chainID)
115+
116+
s := client.SnapshotService(snapshotter)
117+
118+
var mounts []mount.Mount
119+
if context.Bool("rw") {
120+
mounts, err = s.Prepare(ctx, target, chainID)
121+
} else {
122+
mounts, err = s.View(ctx, target, chainID)
123+
}
124+
if err != nil {
125+
if errdefs.IsAlreadyExists(err) {
126+
mounts, err = s.Mounts(ctx, target)
127+
}
128+
if err != nil {
129+
return err
130+
}
131+
}
132+
133+
if err := mount.All(mounts, target); err != nil {
134+
if err := s.Remove(ctx, target); err != nil && !errdefs.IsNotFound(err) {
135+
fmt.Fprintln(context.App.ErrWriter, "Error cleaning up snapshot after mount error:", err)
136+
}
137+
return err
138+
}
139+
140+
fmt.Fprintln(context.App.Writer, target)
141+
return nil
142+
},
143+
}

cmd/ctr/commands/images/unmount.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package images
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/containerd/containerd/cmd/ctr/commands"
7+
"github.com/containerd/containerd/errdefs"
8+
"github.com/containerd/containerd/leases"
9+
"github.com/containerd/containerd/mount"
10+
"github.com/pkg/errors"
11+
"github.com/urfave/cli"
12+
)
13+
14+
/*
15+
Copyright The containerd Authors.
16+
17+
Licensed under the Apache License, Version 2.0 (the "License");
18+
you may not use this file except in compliance with the License.
19+
You may obtain a copy of the License at
20+
21+
http://www.apache.org/licenses/LICENSE-2.0
22+
23+
Unless required by applicable law or agreed to in writing, software
24+
distributed under the License is distributed on an "AS IS" BASIS,
25+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26+
See the License for the specific language governing permissions and
27+
limitations under the License.
28+
*/
29+
30+
var unmountCommand = cli.Command{
31+
Name: "unmount",
32+
Usage: "unmount the image from the target",
33+
ArgsUsage: "[flags] <target>",
34+
Description: "Unmount the image rootfs from the specified target.",
35+
Flags: append(append(commands.RegistryFlags, append(commands.SnapshotterFlags, commands.LabelFlag)...),
36+
cli.BoolFlag{
37+
Name: "rm",
38+
Usage: "remove the snapshot after a successful unmount",
39+
},
40+
),
41+
Action: func(context *cli.Context) error {
42+
var (
43+
target = context.Args().First()
44+
)
45+
if target == "" {
46+
return fmt.Errorf("please provide a target path to mount to")
47+
}
48+
49+
client, ctx, cancel, err := commands.NewClient(context)
50+
if err != nil {
51+
return err
52+
}
53+
defer cancel()
54+
55+
if err := mount.UnmountAll(target, 0); err != nil {
56+
return err
57+
}
58+
59+
if context.Bool("rm") {
60+
snapshotter := context.String("snapshotter")
61+
s := client.SnapshotService(snapshotter)
62+
if err := client.LeasesService().Delete(ctx, leases.Lease{ID: target}); err != nil && !errdefs.IsNotFound(err) {
63+
return errors.Wrap(err, "error deleting lease")
64+
}
65+
if err := s.Remove(ctx, target); err != nil && !errdefs.IsNotFound(err) {
66+
return errors.Wrap(err, "error removing snapshot")
67+
}
68+
}
69+
70+
fmt.Fprintln(context.App.Writer, target)
71+
return nil
72+
},
73+
}

0 commit comments

Comments
 (0)