mirror of https://github.com/portainer/portainer
fix(docker): avoid specifying the MAC address of container for Docker API < v1.44 BE-10880 (#12178)
parent
273ea5df23
commit
04e9ee3b3e
|
@ -4,15 +4,17 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||||
|
"github.com/portainer/portainer/api/docker/images"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockercontainer "github.com/docker/docker/api/types/container"
|
dockercontainer "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
portainer "github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
|
||||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
|
||||||
"github.com/portainer/portainer/api/docker/images"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,6 +32,44 @@ func NewContainerService(factory *dockerclient.ClientFactory, dataStore dataserv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyVersionConstraint uses the version to apply a transformation function to
|
||||||
|
// the value when the constraint is satisfied
|
||||||
|
func applyVersionConstraint[T any](currentVersion, versionConstraint string, value T, transform func(T) T) (T, error) {
|
||||||
|
newValue := value
|
||||||
|
|
||||||
|
constraint, err := semver.NewConstraint(versionConstraint)
|
||||||
|
if err != nil {
|
||||||
|
return newValue, errors.New("invalid version constraint specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVer, err := semver.NewVersion(currentVersion)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("Unable to parse the Docker client version")
|
||||||
|
|
||||||
|
return newValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if satisfiesConstraint, _ := constraint.Validate(currentVer); satisfiesConstraint {
|
||||||
|
newValue = transform(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearMacAddrs(n network.NetworkingConfig) network.NetworkingConfig {
|
||||||
|
netConfig := network.NetworkingConfig{
|
||||||
|
EndpointsConfig: make(map[string]*network.EndpointSettings),
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range n.EndpointsConfig {
|
||||||
|
endpointConfig := n.EndpointsConfig[k].Copy()
|
||||||
|
endpointConfig.MacAddress = ""
|
||||||
|
netConfig.EndpointsConfig[k] = endpointConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return netConfig
|
||||||
|
}
|
||||||
|
|
||||||
// Recreate a container
|
// Recreate a container
|
||||||
func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.Endpoint, containerId string, forcePullImage bool, imageTag, nodeName string) (*types.ContainerJSON, error) {
|
func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.Endpoint, containerId string, forcePullImage bool, imageTag, nodeName string) (*types.ContainerJSON, error) {
|
||||||
cli, err := c.factory.CreateClient(endpoint, nodeName, nil)
|
cli, err := c.factory.CreateClient(endpoint, nodeName, nil)
|
||||||
|
@ -90,7 +130,7 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||||
return nil, errors.Wrap(err, "rename container error")
|
return nil, errors.Wrap(err, "rename container error")
|
||||||
}
|
}
|
||||||
|
|
||||||
networkWithCreation := network.NetworkingConfig{
|
initialNetwork := network.NetworkingConfig{
|
||||||
EndpointsConfig: make(map[string]*network.EndpointSettings),
|
EndpointsConfig: make(map[string]*network.EndpointSettings),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,10 +143,10 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. get the first network attached to the current container
|
// 5. get the first network attached to the current container
|
||||||
if len(networkWithCreation.EndpointsConfig) == 0 {
|
if len(initialNetwork.EndpointsConfig) == 0 {
|
||||||
// Retrieve the first network that is linked to the present container, which
|
// Retrieve the first network that is linked to the present container, which
|
||||||
// will be utilized when creating the container.
|
// will be utilized when creating the container.
|
||||||
networkWithCreation.EndpointsConfig[name] = network
|
initialNetwork.EndpointsConfig[name] = network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.sr.enable()
|
c.sr.enable()
|
||||||
|
@ -130,7 +170,15 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||||
// to retain the same network settings we have to connect on creation to one of the old
|
// to retain the same network settings we have to connect on creation to one of the old
|
||||||
// container's networks, and connect to the other networks after creation.
|
// container's networks, and connect to the other networks after creation.
|
||||||
// see: https://portainer.atlassian.net/browse/EE-5448
|
// see: https://portainer.atlassian.net/browse/EE-5448
|
||||||
create, err := cli.ContainerCreate(ctx, container.Config, container.HostConfig, &networkWithCreation, nil, container.Name)
|
|
||||||
|
// Docker API < 1.44 does not support specifying MAC addresses
|
||||||
|
// https://github.com/moby/moby/blob/6aea26b431ea152a8b085e453da06ea403f89886/client/container_create.go#L44-L46
|
||||||
|
initialNetwork, err = applyVersionConstraint(cli.ClientVersion(), "< 1.44", initialNetwork, clearMacAddrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
create, err := cli.ContainerCreate(ctx, container.Config, container.HostConfig, &initialNetwork, nil, container.Name)
|
||||||
|
|
||||||
c.sr.push(func() {
|
c.sr.push(func() {
|
||||||
log.Debug().Str("container_id", create.ID).Msg("removing the new container")
|
log.Debug().Str("container_id", create.ID).Msg("removing the new container")
|
||||||
|
@ -150,8 +198,7 @@ func (c *ContainerService) Recreate(ctx context.Context, endpoint *portainer.End
|
||||||
log.Debug().Str("container_id", newContainerId).Msg("connecting networks to container")
|
log.Debug().Str("container_id", newContainerId).Msg("connecting networks to container")
|
||||||
networks := container.NetworkSettings.Networks
|
networks := container.NetworkSettings.Networks
|
||||||
for key, network := range networks {
|
for key, network := range networks {
|
||||||
_, ok := networkWithCreation.EndpointsConfig[key]
|
if _, ok := initialNetwork.EndpointsConfig[key]; ok {
|
||||||
if ok {
|
|
||||||
// skip the network that is used during container creation
|
// skip the network that is used during container creation
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplyVersionConstraint(t *testing.T) {
|
||||||
|
initialNet := network.NetworkingConfig{
|
||||||
|
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||||
|
"key1": {
|
||||||
|
MacAddress: "mac1",
|
||||||
|
EndpointID: "endpointID1",
|
||||||
|
},
|
||||||
|
"key2": {
|
||||||
|
MacAddress: "mac2",
|
||||||
|
EndpointID: "endpointID2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
f := func(currentVer string, constraint string, success, emptyMac bool) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
transformedNet, err := applyVersionConstraint(currentVer, constraint, initialNet, clearMacAddrs)
|
||||||
|
if success {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(t, transformedNet.EndpointsConfig, len(initialNet.EndpointsConfig))
|
||||||
|
|
||||||
|
for k := range initialNet.EndpointsConfig {
|
||||||
|
if emptyMac {
|
||||||
|
require.NotEqual(t, initialNet.EndpointsConfig[k], transformedNet.EndpointsConfig[k])
|
||||||
|
require.Empty(t, transformedNet.EndpointsConfig[k].MacAddress)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, initialNet.EndpointsConfig[k], transformedNet.EndpointsConfig[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f("1.45", "< 1.44", true, false) // No transformation
|
||||||
|
f("1.43", "< 1.44", true, true) // Transformation
|
||||||
|
f("a.b.", "< 1.44", true, false) // Invalid current version
|
||||||
|
f("1.45", "z 1.44", false, false) // Invalid version constraint
|
||||||
|
}
|
Loading…
Reference in New Issue