mirror of
https://github.com/moby/moby.git
synced 2026-01-12 11:11:44 +00:00
Compare commits
20 Commits
copilot/fi
...
v27.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
662f78c0b1 | ||
|
|
b86d9bdaf3 | ||
|
|
0dbc3ac59e | ||
|
|
276a648ec3 | ||
|
|
22aa07b28f | ||
|
|
23b8b023dd | ||
|
|
bf222d635b | ||
|
|
f8231b52d3 | ||
|
|
b951474404 | ||
|
|
c5794e23ec | ||
|
|
02e24483be | ||
|
|
b70040a8fc | ||
|
|
838330bac3 | ||
|
|
e419e22f29 | ||
|
|
e953d76450 | ||
|
|
861fde8cc9 | ||
|
|
3557077867 | ||
|
|
c95b917167 | ||
|
|
c0ff08acbd | ||
|
|
4587688258 |
@@ -8,12 +8,12 @@ ARG XX_VERSION=1.4.0
|
||||
ARG VPNKIT_VERSION=0.5.0
|
||||
|
||||
ARG DOCKERCLI_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_VERSION=v26.1.0
|
||||
ARG DOCKERCLI_VERSION=v27.0.2
|
||||
# cli version used for integration-cli tests
|
||||
ARG DOCKERCLI_INTEGRATION_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION=v17.06.2-ce
|
||||
ARG BUILDX_VERSION=0.15.1
|
||||
ARG COMPOSE_VERSION=v2.27.1
|
||||
ARG COMPOSE_VERSION=v2.28.1
|
||||
|
||||
ARG SYSTEMD="false"
|
||||
ARG DOCKER_STATIC=1
|
||||
@@ -229,7 +229,7 @@ FROM binary-dummy AS containerd-windows
|
||||
FROM containerd-${TARGETOS} AS containerd
|
||||
|
||||
FROM base AS golangci_lint
|
||||
ARG GOLANGCI_LINT_VERSION=v1.55.2
|
||||
ARG GOLANGCI_LINT_VERSION=v1.59.1
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
GOBIN=/build/ GO111MODULE=on go install "github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" \
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
||||
Target: "/bar",
|
||||
TmpfsOptions: &mount.TmpfsOptions{
|
||||
Options: [][]string{
|
||||
[]string{"exec"},
|
||||
{"exec"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -73,7 +73,7 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
||||
adjustForAPIVersion("1.46", spec)
|
||||
if !reflect.DeepEqual(
|
||||
spec.TaskTemplate.ContainerSpec.Mounts[0].TmpfsOptions.Options,
|
||||
[][]string{[]string{"exec"}},
|
||||
[][]string{{"exec"}},
|
||||
) {
|
||||
t.Error("TmpfsOptions.Options was stripped from spec")
|
||||
}
|
||||
|
||||
@@ -245,18 +245,6 @@ type ContainerState struct {
|
||||
Health *Health `json:",omitempty"`
|
||||
}
|
||||
|
||||
// ContainerNode stores information about the node that a container
|
||||
// is running on. It's only used by the Docker Swarm standalone API
|
||||
type ContainerNode struct {
|
||||
ID string
|
||||
IPAddress string `json:"IP"`
|
||||
Addr string
|
||||
Name string
|
||||
Cpus int
|
||||
Memory int64
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// ContainerJSONBase contains response of Engine API:
|
||||
// GET "/containers/{name:.*}/json"
|
||||
type ContainerJSONBase struct {
|
||||
@@ -270,7 +258,7 @@ type ContainerJSONBase struct {
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
LogPath string
|
||||
Node *ContainerNode `json:",omitempty"` // Node is only propagated by Docker Swarm standalone API
|
||||
Node *ContainerNode `json:",omitempty"` // Deprecated: Node was only propagated by Docker Swarm standalone API. It sill be removed in the next release.
|
||||
Name string
|
||||
RestartCount int
|
||||
Driver string
|
||||
|
||||
@@ -194,3 +194,17 @@ type ImageImportSource image.ImportSource
|
||||
//
|
||||
// Deprecated: use [image.LoadResponse].
|
||||
type ImageLoadResponse = image.LoadResponse
|
||||
|
||||
// ContainerNode stores information about the node that a container
|
||||
// is running on. It's only used by the Docker Swarm standalone API.
|
||||
//
|
||||
// Deprecated: ContainerNode was used for the classic Docker Swarm standalone API. It will be removed in the next release.
|
||||
type ContainerNode struct {
|
||||
ID string
|
||||
IPAddress string `json:"IP"`
|
||||
Addr string
|
||||
Name string
|
||||
Cpus int
|
||||
Memory int64
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ func downloadRemote(remoteURL string) (string, io.ReadCloser, error) {
|
||||
// GetWithStatusError does an http.Get() and returns an error if the
|
||||
// status code is 4xx or 5xx.
|
||||
func GetWithStatusError(address string) (resp *http.Response, err error) {
|
||||
// #nosec G107
|
||||
if resp, err = http.Get(address); err != nil {
|
||||
resp, err = http.Get(address) // #nosec G107 -- ignore G107: Potential HTTP request made with variable url
|
||||
if err != nil {
|
||||
if uerr, ok := err.(*url.Error); ok {
|
||||
if derr, ok := uerr.Err.(*net.DNSError); ok && !derr.IsTimeout {
|
||||
return nil, errdefs.NotFound(err)
|
||||
|
||||
@@ -83,55 +83,3 @@ func TestContainerInspect(t *testing.T) {
|
||||
t.Fatalf("expected `name`, got %s", r.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContainerInspectNode tests that the "Node" field is included in the "inspect"
|
||||
// output. This information is only present when connected to a Swarm standalone API.
|
||||
func TestContainerInspectNode(t *testing.T) {
|
||||
client := &Client{
|
||||
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
content, err := json.Marshal(types.ContainerJSON{
|
||||
ContainerJSONBase: &types.ContainerJSONBase{
|
||||
ID: "container_id",
|
||||
Image: "image",
|
||||
Name: "name",
|
||||
Node: &types.ContainerNode{
|
||||
ID: "container_node_id",
|
||||
Addr: "container_node",
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewReader(content)),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
r, err := client.ContainerInspect(context.Background(), "container_id")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.ID != "container_id" {
|
||||
t.Fatalf("expected `container_id`, got %s", r.ID)
|
||||
}
|
||||
if r.Image != "image" {
|
||||
t.Fatalf("expected `image`, got %s", r.Image)
|
||||
}
|
||||
if r.Name != "name" {
|
||||
t.Fatalf("expected `name`, got %s", r.Name)
|
||||
}
|
||||
if r.Node.ID != "container_node_id" {
|
||||
t.Fatalf("expected `container_node_id`, got %s", r.Node.ID)
|
||||
}
|
||||
if r.Node.Addr != "container_node" {
|
||||
t.Fatalf("expected `container_node`, got %s", r.Node.Addr)
|
||||
}
|
||||
foo, ok := r.Node.Labels["foo"]
|
||||
if foo != "bar" || !ok {
|
||||
t.Fatalf("expected `bar` for label `foo`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
func TestTmpfsOptionsToGRPC(t *testing.T) {
|
||||
options := [][]string{
|
||||
[]string{"noexec"},
|
||||
[]string{"uid", "12345"},
|
||||
{"noexec"},
|
||||
{"uid", "12345"},
|
||||
}
|
||||
|
||||
expected := `[["noexec"],["uid","12345"]]`
|
||||
@@ -21,8 +21,8 @@ func TestTmpfsOptionsFromGRPC(t *testing.T) {
|
||||
options := `[["noexec"],["uid","12345"]]`
|
||||
|
||||
expected := [][]string{
|
||||
[]string{"noexec"},
|
||||
[]string{"uid", "12345"},
|
||||
{"noexec"},
|
||||
{"uid", "12345"},
|
||||
}
|
||||
actual := tmpfsOptionsFromGRPC(options)
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ func TestTmpfsConversion(t *testing.T) {
|
||||
Target: "/bar",
|
||||
Type: mount.TypeTmpfs,
|
||||
TmpfsOptions: &mount.TmpfsOptions{
|
||||
Options: [][]string{[]string{"exec"}},
|
||||
Options: [][]string{{"exec"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -190,7 +190,7 @@ func TestTmpfsConversion(t *testing.T) {
|
||||
Target: "/bar",
|
||||
Type: mount.TypeTmpfs,
|
||||
TmpfsOptions: &mount.TmpfsOptions{
|
||||
Options: [][]string{[]string{"noexec"}},
|
||||
Options: [][]string{{"noexec"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/pkg/userns"
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
@@ -678,7 +679,6 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
|
||||
return d.naiveDiff.ApplyDiff(id, parent, diff)
|
||||
}
|
||||
|
||||
// never reach here if we are running in UserNS
|
||||
applyDir := d.getDiffPath(id)
|
||||
|
||||
logger.Debugf("Applying tar in %s", applyDir)
|
||||
@@ -686,6 +686,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
|
||||
if err := untar(diff, applyDir, &archive.TarOptions{
|
||||
IDMap: d.idMap,
|
||||
WhiteoutFormat: archive.OverlayWhiteoutFormat,
|
||||
InUserNS: userns.RunningInUserNS(),
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -108,10 +108,10 @@ func TestSaveOCI(t *testing.T) {
|
||||
|
||||
testCases := []testCase{
|
||||
// Busybox by tagged name
|
||||
testCase{image: busybox, expectedContainerdRef: "docker.io/library/busybox:latest", expectedOCIRef: "latest"},
|
||||
{image: busybox, expectedContainerdRef: "docker.io/library/busybox:latest", expectedOCIRef: "latest"},
|
||||
|
||||
// Busybox by ID
|
||||
testCase{image: inspectBusybox.ID},
|
||||
{image: inspectBusybox.ID},
|
||||
}
|
||||
|
||||
if testEnv.DaemonInfo.OSType != "windows" {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@@ -1055,3 +1056,38 @@ func TestPortMappedHairpin(t *testing.T) {
|
||||
defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true})
|
||||
assert.Check(t, is.Contains(res.Stderr.String(), "404 Not Found"))
|
||||
}
|
||||
|
||||
// Check that a container on an IPv4-only network can have a port mapping
|
||||
// from a specific IPv6 host address (using docker-proxy).
|
||||
// Regression test for https://github.com/moby/moby/issues/48067 (which
|
||||
// is about incorrectly reporting this as invalid config).
|
||||
func TestProxy4To6(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "uses bridge network and docker-proxy")
|
||||
skip.If(t, testEnv.IsRootless)
|
||||
|
||||
ctx := setupTest(t)
|
||||
d := daemon.New(t)
|
||||
d.StartWithBusybox(ctx, t)
|
||||
defer d.Stop(t)
|
||||
|
||||
c := d.NewClientT(t)
|
||||
defer c.Close()
|
||||
|
||||
const netName = "ipv4net"
|
||||
network.CreateNoError(ctx, t, c, netName)
|
||||
|
||||
serverId := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(netName),
|
||||
container.WithExposedPorts("80"),
|
||||
container.WithPortMap(nat.PortMap{"80": {{HostIP: "::1"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, serverId, containertypes.RemoveOptions{Force: true})
|
||||
|
||||
inspect := container.Inspect(ctx, t, c, serverId)
|
||||
hostPort := inspect.NetworkSettings.Ports["80/tcp"][0].HostPort
|
||||
|
||||
resp, err := http.Get("http://[::1]:" + hostPort)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(resp.StatusCode, 404))
|
||||
}
|
||||
|
||||
@@ -60,9 +60,7 @@ func (n *bridgeNetwork) addPortMappings(
|
||||
}
|
||||
|
||||
disableNAT4, disableNAT6 := n.getNATDisabled()
|
||||
if err := validatePortBindings(cfg,
|
||||
!disableNAT4 && len(containerIPv4) > 0,
|
||||
!disableNAT6 && len(containerIPv6) > 0); err != nil {
|
||||
if err := validatePortBindings(cfg, !disableNAT4, !disableNAT6, containerIPv6); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -157,27 +155,32 @@ const validationErrLimit = 6
|
||||
// docker-proxy is used to forward between the default IPv6 address and the
|
||||
// container's IPv4. So, simply disallowing a non-zero IPv6 default when NAT6
|
||||
// is disabled for the network would be incorrect.)
|
||||
func validatePortBindings(pbs []types.PortBinding, nat4, nat6 bool) error {
|
||||
func validatePortBindings(pbs []types.PortBinding, nat4, nat6 bool, cIPv6 net.IP) error {
|
||||
var errs []error
|
||||
for i := range pbs {
|
||||
pb := &pbs[i]
|
||||
disallowHostPort := false
|
||||
if !nat4 && len(pb.HostIP) > 0 && pb.HostIP.To4() != nil && !pb.HostIP.Equal(net.IPv4zero) {
|
||||
// There's no NAT4, so don't allow a nonzero IPv4 host address in the mapping. The port will
|
||||
// accessible via any host interface.
|
||||
// be accessible via any host interface.
|
||||
errs = append(errs,
|
||||
fmt.Errorf("NAT is disabled, omit host address in port mapping %s, or use 0.0.0.0::%d to open port %d for IPv4-only",
|
||||
pb, pb.Port, pb.Port))
|
||||
// The mapping is IPv4-specific but there's no NAT4, so a host port would make no sense.
|
||||
disallowHostPort = true
|
||||
} else if !nat6 && len(pb.HostIP) > 0 && pb.HostIP.To4() == nil && !pb.HostIP.Equal(net.IPv6zero) {
|
||||
// There's no NAT6, so don't allow an IPv6 host address in the mapping. The port will
|
||||
// accessible via any host interface.
|
||||
errs = append(errs,
|
||||
fmt.Errorf("NAT is disabled, omit host address in port mapping %s, or use [::]::%d to open port %d for IPv6-only",
|
||||
pb, pb.Port, pb.Port))
|
||||
// The mapping is IPv6-specific but there's no NAT6, so a host port would make no sense.
|
||||
disallowHostPort = true
|
||||
// If the container has no IPv6 address, the userland proxy will proxy between the
|
||||
// host's IPv6 address and the container's IPv4. So, even with no NAT6, it's ok for
|
||||
// an IPv6 port mapping to include a specific host address or port.
|
||||
if len(cIPv6) > 0 {
|
||||
// There's no NAT6, so don't allow an IPv6 host address in the mapping. The port will
|
||||
// accessible via any host interface.
|
||||
errs = append(errs,
|
||||
fmt.Errorf("NAT is disabled, omit host address in port mapping %s, or use [::]::%d to open port %d for IPv6-only",
|
||||
pb, pb.Port, pb.Port))
|
||||
// The mapping is IPv6-specific but there's no NAT6, so a host port would make no sense.
|
||||
disallowHostPort = true
|
||||
}
|
||||
} else if !nat4 && !nat6 {
|
||||
// There's no NAT, so it would make no sense to specify a host port.
|
||||
disallowHostPort = true
|
||||
|
||||
@@ -186,6 +186,7 @@ func TestValidatePortBindings(t *testing.T) {
|
||||
name string
|
||||
nat4 bool
|
||||
nat6 bool
|
||||
ctrIPv6 net.IP
|
||||
pbs []types.PortBinding
|
||||
expErrs []string
|
||||
}{
|
||||
@@ -196,7 +197,8 @@ func TestValidatePortBindings(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat with addrs",
|
||||
name: "no nat with addrs",
|
||||
ctrIPv6: newIPNet(t, "fd2c:b48c:69fb::2/128").IP,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, Port: 80},
|
||||
@@ -246,7 +248,16 @@ func TestValidatePortBindings(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat and addrs and ports",
|
||||
name: "nat4 and addrs and ports",
|
||||
nat4: true,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat and addrs and ports",
|
||||
ctrIPv6: newIPNet(t, "fd2c:b48c:69fb::2/128").IP,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, HostPort: 8080, Port: 80},
|
||||
@@ -258,6 +269,17 @@ func TestValidatePortBindings(t *testing.T) {
|
||||
"host port must not be specified in mapping [2001:db8::2]:8080:80/tcp because NAT is disabled",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat no ctrIPv6 and addrs and ports",
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
expErrs: []string{
|
||||
"NAT is disabled, omit host address in port mapping 233.252.0.2:8080:80/tcp, or use 0.0.0.0::80 to open port 80 for IPv4-only",
|
||||
"host port must not be specified in mapping 233.252.0.2:8080:80/tcp because NAT is disabled",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "max errs reached",
|
||||
pbs: []types.PortBinding{
|
||||
@@ -283,7 +305,7 @@ func TestValidatePortBindings(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := validatePortBindings(tc.pbs, tc.nat4, tc.nat6)
|
||||
err := validatePortBindings(tc.pbs, tc.nat4, tc.nat6, tc.ctrIPv6)
|
||||
if tc.expErrs == nil {
|
||||
assert.Check(t, err)
|
||||
} else {
|
||||
|
||||
@@ -164,8 +164,6 @@ func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip.
|
||||
pdf := aSpace.predefined[pdfID]
|
||||
|
||||
if allocated.Overlaps(pdf.Base) {
|
||||
it.Inc()
|
||||
|
||||
if allocated.Bits() <= pdf.Base.Bits() {
|
||||
// The current 'allocated' prefix is bigger than the 'pdf'
|
||||
// network, thus the block is fully overlapped.
|
||||
@@ -195,6 +193,8 @@ func (aSpace *addrSpace) allocatePredefinedPool(reserved []netip.Prefix) (netip.
|
||||
return makeAlloc(afterPrev), nil
|
||||
}
|
||||
|
||||
it.Inc()
|
||||
|
||||
if netiputil.LastAddr(allocated) == netiputil.LastAddr(pdf.Base) {
|
||||
// The last address of the current 'allocated' prefix is the
|
||||
// same as the last address of the 'pdf' network, it's fully
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package defaultipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
@@ -387,3 +388,113 @@ func TestStaticAllocation(t *testing.T) {
|
||||
netip.MustParsePrefix("192.168.3.0/24"),
|
||||
}, cmpopts.EquateComparable(netip.Prefix{})))
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/moby/moby/issues/48069
|
||||
func TestPoolAllocateAndRelease(t *testing.T) {
|
||||
type testClosures struct {
|
||||
alloc func(netname string)
|
||||
release func(netname string)
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
predefined []*ipamutils.NetworkToSplit
|
||||
reserved []netip.Prefix
|
||||
calls []func(tcs testClosures)
|
||||
tcs testClosures
|
||||
}{
|
||||
{
|
||||
name: "allocate after reserved",
|
||||
predefined: []*ipamutils.NetworkToSplit{
|
||||
{Base: netip.MustParsePrefix("10.0.0.0/24"), Size: 24},
|
||||
{Base: netip.MustParsePrefix("10.0.1.0/24"), Size: 24},
|
||||
{Base: netip.MustParsePrefix("10.1.0.0/16"), Size: 24},
|
||||
},
|
||||
reserved: []netip.Prefix{
|
||||
netip.MustParsePrefix("10.0.0.0/16"),
|
||||
},
|
||||
calls: []func(tcs testClosures){
|
||||
func(tcs testClosures) { tcs.alloc("n1") },
|
||||
func(tcs testClosures) { tcs.alloc("n2") },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reallocate first subnet",
|
||||
predefined: []*ipamutils.NetworkToSplit{
|
||||
{Base: netip.MustParsePrefix("10.0.0.0/8"), Size: 24},
|
||||
},
|
||||
calls: []func(tcs testClosures){
|
||||
func(tcs testClosures) { tcs.alloc("n1") },
|
||||
func(tcs testClosures) { tcs.alloc("n2") },
|
||||
func(tcs testClosures) { tcs.alloc("n3") },
|
||||
func(tcs testClosures) { tcs.release("n1") },
|
||||
func(tcs testClosures) { tcs.alloc("n4") },
|
||||
func(tcs testClosures) { tcs.alloc("n5") },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reallocate after release",
|
||||
predefined: []*ipamutils.NetworkToSplit{
|
||||
{Base: netip.MustParsePrefix("10.0.0.0/8"), Size: 24},
|
||||
},
|
||||
calls: []func(tcs testClosures){
|
||||
func(tcs testClosures) { tcs.alloc("n1") },
|
||||
func(tcs testClosures) { tcs.alloc("n2") },
|
||||
func(tcs testClosures) { tcs.alloc("n3") },
|
||||
func(tcs testClosures) { tcs.alloc("n4") },
|
||||
func(tcs testClosures) { tcs.release("n2") },
|
||||
func(tcs testClosures) { tcs.release("n3") },
|
||||
func(tcs testClosures) { tcs.alloc("n5") },
|
||||
func(tcs testClosures) { tcs.alloc("n6") },
|
||||
func(tcs testClosures) { tcs.alloc("n7") },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
as, err := newAddrSpace(tc.predefined)
|
||||
assert.NilError(t, err)
|
||||
subnetToNetname := map[netip.Prefix]string{}
|
||||
netnameToSubnet := map[string]netip.Prefix{}
|
||||
|
||||
// To avoid passing as,subnetToNetname,netnameToSubnet into each of the
|
||||
// functions in tc.calls (cluttering the list of testcases), create closures
|
||||
// that use them and pass those.
|
||||
tcs := testClosures{
|
||||
// Allocate a pool for netname, check that a subnet is returned that
|
||||
// isn't already allocated, and doesn't overlap with a reserved range.
|
||||
alloc: func(netname string) {
|
||||
subnet, err := as.allocatePredefinedPool(tc.reserved)
|
||||
assert.NilError(t, err)
|
||||
|
||||
otherNetname, exists := subnetToNetname[subnet]
|
||||
assert.Assert(t, !exists, fmt.Sprintf(
|
||||
"subnet %s allocated to %s, reallocated for %s", subnet, otherNetname, netname))
|
||||
for _, reserved := range tc.reserved {
|
||||
assert.Assert(t, !reserved.Overlaps(subnet),
|
||||
fmt.Sprintf("subnet %s allocated in reserved range %s", subnet, reserved))
|
||||
}
|
||||
|
||||
subnetToNetname[subnet] = netname
|
||||
netnameToSubnet[netname] = subnet
|
||||
},
|
||||
// Release a pool for a netname - the test must ensure netname currently
|
||||
// has an allocated subnet.
|
||||
release: func(netname string) {
|
||||
subnet, ok := netnameToSubnet[netname]
|
||||
assert.Assert(t, ok)
|
||||
err := as.releaseSubnet(subnet, netip.Prefix{})
|
||||
assert.NilError(t, err)
|
||||
|
||||
delete(netnameToSubnet, netname)
|
||||
delete(subnetToNetname, subnet)
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range tc.calls {
|
||||
f(tcs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/pkg/userns"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
@@ -675,9 +674,11 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
||||
inUserns, bestEffortXattrs bool
|
||||
chownOpts *idtools.Identity
|
||||
)
|
||||
|
||||
// TODO(thaJeztah): make opts a required argument.
|
||||
if opts != nil {
|
||||
Lchown = !opts.NoLchown
|
||||
inUserns = opts.InUserNS
|
||||
inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally.
|
||||
chownOpts = opts.ChownOpts
|
||||
bestEffortXattrs = opts.BestEffortXattrs
|
||||
}
|
||||
@@ -712,6 +713,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
||||
|
||||
case tar.TypeBlock, tar.TypeChar:
|
||||
if inUserns { // cannot create devices in a userns
|
||||
log.G(context.TODO()).WithFields(log.Fields{"path": path, "type": hdr.Typeflag}).Debug("skipping device nodes in a userns")
|
||||
return nil
|
||||
}
|
||||
// Handle this is an OS-specific way
|
||||
@@ -722,6 +724,11 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
||||
case tar.TypeFifo:
|
||||
// Handle this is an OS-specific way
|
||||
if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
|
||||
if inUserns && errors.Is(err, syscall.EPERM) {
|
||||
// In most cases, cannot create a fifo if running in user namespace
|
||||
log.G(context.TODO()).WithFields(log.Fields{"error": err, "path": path, "type": hdr.Typeflag}).Debug("creating fifo node in a userns")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -765,7 +772,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
|
||||
}
|
||||
if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil {
|
||||
msg := "failed to Lchown %q for UID %d, GID %d"
|
||||
if errors.Is(err, syscall.EINVAL) && userns.RunningInUserNS() {
|
||||
if inUserns && errors.Is(err, syscall.EINVAL) {
|
||||
msg += " (try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)"
|
||||
}
|
||||
return errors.Wrapf(err, msg, path, hdr.Uid, hdr.Gid)
|
||||
@@ -871,11 +878,6 @@ func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Tarballer{
|
||||
// Fix the source path to work with long path names. This is a no-op
|
||||
// on platforms other than Windows.
|
||||
@@ -885,7 +887,7 @@ func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) {
|
||||
pipeReader: pipeReader,
|
||||
pipeWriter: pipeWriter,
|
||||
compressWriter: compressWriter,
|
||||
whiteoutConverter: whiteoutConverter,
|
||||
whiteoutConverter: getWhiteoutConverter(options.WhiteoutFormat),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1080,10 +1082,7 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err
|
||||
defer pools.BufioReader32KPool.Put(trBuf)
|
||||
|
||||
var dirs []*tar.Header
|
||||
whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat)
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
loop:
|
||||
|
||||
@@ -12,14 +12,11 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) (tarWhiteoutConverter, error) {
|
||||
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||
if format == OverlayWhiteoutFormat {
|
||||
if inUserNS {
|
||||
return nil, errors.New("specifying OverlayWhiteoutFormat is not allowed in userns")
|
||||
}
|
||||
return overlayWhiteoutConverter{}, nil
|
||||
return overlayWhiteoutConverter{}
|
||||
}
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
type overlayWhiteoutConverter struct{}
|
||||
@@ -54,7 +51,7 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
|
||||
wo = &tar.Header{
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: hdr.Mode & int64(os.ModePerm),
|
||||
Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir),
|
||||
Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
|
||||
Size: 0,
|
||||
Uid: hdr.Uid,
|
||||
Uname: hdr.Uname,
|
||||
@@ -62,7 +59,7 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
|
||||
Gname: hdr.Gname,
|
||||
AccessTime: hdr.AccessTime,
|
||||
ChangeTime: hdr.ChangeTime,
|
||||
} // #nosec G305 -- An archive is being created, not extracted.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
package archive // import "github.com/docker/docker/pkg/archive"
|
||||
|
||||
func getWhiteoutConverter(format WhiteoutFormat, inUserNS bool) (tarWhiteoutConverter, error) {
|
||||
return nil, nil
|
||||
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/containerd/pkg/userns"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -95,7 +94,10 @@ func getFileUIDGID(stat interface{}) (idtools.Identity, error) {
|
||||
}
|
||||
|
||||
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
|
||||
// createTarFile to handle the following types of header: Block; Char; Fifo
|
||||
// createTarFile to handle the following types of header: Block; Char; Fifo.
|
||||
//
|
||||
// Creating device nodes is not supported when running in a user namespace,
|
||||
// produces a [syscall.EPERM] in most cases.
|
||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
||||
mode := uint32(hdr.Mode & 0o7777)
|
||||
switch hdr.Typeflag {
|
||||
@@ -107,12 +109,7 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
||||
mode |= unix.S_IFIFO
|
||||
}
|
||||
|
||||
err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor)))
|
||||
if errors.Is(err, syscall.EPERM) && userns.RunningInUserNS() {
|
||||
// In most cases, cannot create a device if running in user namespace
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
return system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor)))
|
||||
}
|
||||
|
||||
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
||||
|
||||
Reference in New Issue
Block a user