mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Merge pull request #51675 from robmry/nri-mounts
NRI: allow plugins to add mounts
This commit is contained in:
@@ -27,6 +27,8 @@ import (
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/nri/pkg/adaptation"
|
||||
nrilog "github.com/containerd/nri/pkg/log"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
"github.com/moby/moby/v2/daemon/container"
|
||||
"github.com/moby/moby/v2/daemon/internal/rootless"
|
||||
"github.com/moby/moby/v2/daemon/pkg/opts"
|
||||
@@ -245,7 +247,7 @@ func containerToNRI(ctr *container.Container) (*adaptation.PodSandbox, *adaptati
|
||||
Id: ctr.ID,
|
||||
PodSandboxId: ctr.ID,
|
||||
Name: ctr.Name,
|
||||
State: adaptation.ContainerState_CONTAINER_UNKNOWN,
|
||||
State: stateToNRI(ctr.State),
|
||||
Labels: ctr.Config.Labels,
|
||||
Annotations: ctr.HostConfig.Annotations,
|
||||
Args: ctr.Config.Cmd,
|
||||
@@ -275,6 +277,23 @@ func containerToNRI(ctr *container.Container) (*adaptation.PodSandbox, *adaptati
|
||||
return nriPod, nriCtr, nil
|
||||
}
|
||||
|
||||
func stateToNRI(state *container.State) adaptation.ContainerState {
|
||||
log.G(context.TODO()).Errorf("Mapping container state %q to NRI", state.State())
|
||||
switch state.State() {
|
||||
case containertypes.StateCreated:
|
||||
// CONTAINER_CREATED will be used before the container is started, including for the
|
||||
// CreateContainer hook (during container creation).
|
||||
return adaptation.ContainerState_CONTAINER_CREATED
|
||||
case containertypes.StateRunning:
|
||||
return adaptation.ContainerState_CONTAINER_RUNNING
|
||||
case containertypes.StatePaused, containertypes.StateRestarting:
|
||||
return adaptation.ContainerState_CONTAINER_PAUSED
|
||||
case containertypes.StateRemoving, containertypes.StateExited, containertypes.StateDead:
|
||||
return adaptation.ContainerState_CONTAINER_STOPPED
|
||||
}
|
||||
return adaptation.ContainerState_CONTAINER_UNKNOWN
|
||||
}
|
||||
|
||||
func applyAdjustments(ctx context.Context, ctr *container.Container, adj *adaptation.ContainerAdjustment) error {
|
||||
if adj == nil {
|
||||
return nil
|
||||
@@ -282,6 +301,9 @@ func applyAdjustments(ctx context.Context, ctr *container.Container, adj *adapta
|
||||
if err := applyEnvVars(ctx, ctr, adj.Env); err != nil {
|
||||
return fmt.Errorf("applying environment variable adjustments: %w", err)
|
||||
}
|
||||
if err := applyMounts(ctx, ctr, adj.Mounts); err != nil {
|
||||
return fmt.Errorf("applying mount adjustments: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -308,3 +330,25 @@ func applyEnvVars(ctx context.Context, ctr *container.Container, envVars []*adap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyMounts(ctx context.Context, ctr *container.Container, mounts []*adaptation.Mount) error {
|
||||
for _, m := range mounts {
|
||||
var ro bool
|
||||
for _, opt := range m.Options {
|
||||
switch opt {
|
||||
case "ro", "readonly":
|
||||
ro = true
|
||||
default:
|
||||
return fmt.Errorf("mount option %q is not supported", opt)
|
||||
}
|
||||
}
|
||||
log.G(ctx).Debugf("Applying NRI mount: type=%s source=%s target=%s ro=%t", m.Type, m.Source, m.Destination, ro)
|
||||
ctr.HostConfig.Mounts = append(ctr.HostConfig.Mounts, mount.Mount{
|
||||
Type: mount.Type(m.Type),
|
||||
Source: m.Source,
|
||||
Target: m.Destination,
|
||||
ReadOnly: ro,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package nri
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/nri/pkg/api"
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/moby/moby/v2/integration/internal/container"
|
||||
"github.com/moby/moby/v2/internal/testutil"
|
||||
@@ -68,3 +70,123 @@ func TestNRIContainerCreateEnvVarMod(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNRIContainerCreateAddMount(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "cannot start a separate daemon with NRI enabled on Windows")
|
||||
skip.If(t, testEnv.IsRootless)
|
||||
|
||||
ctx := testutil.StartSpan(baseContext, t)
|
||||
|
||||
sockPath := filepath.Join(t.TempDir(), "nri.sock")
|
||||
|
||||
d := daemon.New(t)
|
||||
d.StartWithBusybox(ctx, t,
|
||||
"--nri-opts=enable=true,socket-path="+sockPath,
|
||||
"--iptables=false", "--ip6tables=false",
|
||||
)
|
||||
defer d.Stop(t)
|
||||
c := d.NewClientT(t)
|
||||
|
||||
// Create and populate a directory for containers to mount.
|
||||
dirToMount := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dirToMount, "testfile.txt"), []byte("hello\n"), 0o644); err != nil {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
const (
|
||||
mountPoint = "/mountpoint"
|
||||
ctrTestFile = "/mountpoint/testfile.txt"
|
||||
exitOk = 0
|
||||
exitFail = 1
|
||||
)
|
||||
|
||||
// Create and populate a volume.
|
||||
const volName = "nri-test-volume"
|
||||
_, err := c.VolumeCreate(ctx, client.VolumeCreateOptions{Name: volName})
|
||||
assert.NilError(t, err)
|
||||
defer func() {
|
||||
_, _ = c.VolumeRemove(ctx, volName, client.VolumeRemoveOptions{Force: true})
|
||||
}()
|
||||
// Populate the volume with a test file.
|
||||
_ = container.Run(ctx, t, c,
|
||||
container.WithAutoRemove,
|
||||
container.WithMount(mount.Mount{Type: "volume", Source: volName, Target: mountPoint}),
|
||||
container.WithCmd("sh", "-c", "echo hello > "+ctrTestFile),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctrCreateAdj *api.ContainerAdjustment
|
||||
|
||||
expMountRead int
|
||||
expMountWrite int
|
||||
}{
|
||||
{
|
||||
name: "mount/bind/ro",
|
||||
ctrCreateAdj: &api.ContainerAdjustment{Mounts: []*api.Mount{{
|
||||
Type: "bind",
|
||||
Source: dirToMount,
|
||||
Destination: mountPoint,
|
||||
Options: []string{"ro"},
|
||||
}}},
|
||||
expMountRead: exitOk,
|
||||
expMountWrite: exitFail,
|
||||
},
|
||||
{
|
||||
name: "mount/bind/rw",
|
||||
ctrCreateAdj: &api.ContainerAdjustment{Mounts: []*api.Mount{{
|
||||
Type: "bind",
|
||||
Source: dirToMount,
|
||||
Destination: mountPoint,
|
||||
}}},
|
||||
expMountRead: exitOk,
|
||||
expMountWrite: exitOk,
|
||||
},
|
||||
{
|
||||
name: "mount/volume/ro",
|
||||
ctrCreateAdj: &api.ContainerAdjustment{Mounts: []*api.Mount{{
|
||||
Type: "volume",
|
||||
Source: volName,
|
||||
Destination: mountPoint,
|
||||
Options: []string{"ro"},
|
||||
}}},
|
||||
expMountRead: exitOk,
|
||||
expMountWrite: exitFail,
|
||||
},
|
||||
{
|
||||
name: "mount/volume/rw",
|
||||
ctrCreateAdj: &api.ContainerAdjustment{Mounts: []*api.Mount{{
|
||||
Type: "volume",
|
||||
Source: volName,
|
||||
Destination: mountPoint,
|
||||
}}},
|
||||
expMountRead: exitOk,
|
||||
expMountWrite: exitOk,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
stopPlugin := startBuiltinPlugin(ctx, t, builtinPluginConfig{
|
||||
pluginName: "nri-test-plugin",
|
||||
pluginIdx: "00",
|
||||
sockPath: sockPath,
|
||||
ctrCreateAdj: tc.ctrCreateAdj,
|
||||
})
|
||||
defer stopPlugin()
|
||||
|
||||
ctrId := container.Run(ctx, t, c)
|
||||
defer func() { _, _ = c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true}) }()
|
||||
|
||||
res, err := container.Exec(ctx, c, ctrId, []string{"cat", ctrTestFile})
|
||||
if assert.Check(t, err) {
|
||||
assert.Check(t, is.Equal(res.ExitCode, tc.expMountRead))
|
||||
assert.Check(t, is.Equal(res.Stdout(), "hello\n"))
|
||||
}
|
||||
res, err = container.Exec(ctx, c, ctrId, []string{"touch", ctrTestFile})
|
||||
if assert.Check(t, err) {
|
||||
assert.Check(t, is.Equal(res.ExitCode, tc.expMountWrite))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user