mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
fix(container/network): recreate container changes static IP [EE-5448] (#8960)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portainer.io>
This commit is contained in:
parent
d340c4ea96
commit
96de026eba
35 changed files with 1651 additions and 491 deletions
184
api/docker/images/digest.go
Normal file
184
api/docker/images/digest.go
Normal file
|
@ -0,0 +1,184 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockerclient "github.com/portainer/portainer/api/docker/client"
|
||||
|
||||
"github.com/containers/image/v5/docker"
|
||||
imagetypes "github.com/containers/image/v5/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Options holds docker registry object options
|
||||
type Options struct {
|
||||
Auth imagetypes.DockerAuthConfig
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type DigestClient struct {
|
||||
clientFactory *dockerclient.ClientFactory
|
||||
opts Options
|
||||
sysCtx *imagetypes.SystemContext
|
||||
registryClient *RegistryClient
|
||||
}
|
||||
|
||||
func NewClientWithRegistry(registryClient *RegistryClient, clientFactory *dockerclient.ClientFactory) *DigestClient {
|
||||
return &DigestClient{
|
||||
clientFactory: clientFactory,
|
||||
registryClient: registryClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DigestClient) RemoteDigest(image Image) (digest.Digest, error) {
|
||||
ctx, cancel := c.timeoutContext()
|
||||
defer cancel()
|
||||
// Docker references with both a tag and digest are currently not supported
|
||||
if image.Tag != "" && image.Digest != "" {
|
||||
err := image.trimDigest()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
rmRef, err := ParseReference(image.String())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Cannot parse the image reference")
|
||||
}
|
||||
|
||||
sysCtx := c.sysCtx
|
||||
if c.registryClient != nil {
|
||||
username, password, err := c.registryClient.RegistryAuth(image)
|
||||
if err != nil {
|
||||
log.Info().Str("image up to date indicator", image.String()).Msg("No environment registry credentials found, using anonymous access")
|
||||
} else {
|
||||
sysCtx = &imagetypes.SystemContext{
|
||||
DockerAuthConfig: &imagetypes.DockerAuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve remote digest through HEAD request
|
||||
rmDigest, err := docker.GetDigest(ctx, sysCtx, rmRef)
|
||||
if err != nil {
|
||||
// fallback to public registry for hub
|
||||
if image.HubLink != "" {
|
||||
rmDigest, err = docker.GetDigest(ctx, c.sysCtx, rmRef)
|
||||
if err == nil {
|
||||
return rmDigest, nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Err(err).Msg("get remote digest error")
|
||||
|
||||
return "", errors.Wrap(err, "Cannot get image digest from HEAD request")
|
||||
}
|
||||
|
||||
return rmDigest, nil
|
||||
}
|
||||
|
||||
func ParseLocalImage(inspect types.ImageInspect) (*Image, error) {
|
||||
if IsLocalImage(inspect) || IsDanglingImage(inspect) {
|
||||
return nil, errors.New("the image is not regular")
|
||||
}
|
||||
|
||||
fromRepoDigests, err := ParseImage(ParseImageOptions{
|
||||
// including image name but no tag
|
||||
Name: inspect.RepoDigests[0],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if IsNoTagImage(inspect) {
|
||||
return &fromRepoDigests, nil
|
||||
}
|
||||
|
||||
fromRepoTags, err := ParseImage(ParseImageOptions{
|
||||
Name: inspect.RepoTags[0],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fromRepoDigests.Tag = fromRepoTags.Tag
|
||||
|
||||
return &fromRepoDigests, nil
|
||||
}
|
||||
|
||||
func ParseRepoDigests(repoDigests []string) []digest.Digest {
|
||||
digests := make([]digest.Digest, 0)
|
||||
for _, repoDigest := range repoDigests {
|
||||
d := ParseRepoDigest(repoDigest)
|
||||
if d == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
digests = append(digests, d)
|
||||
}
|
||||
|
||||
return digests
|
||||
}
|
||||
|
||||
func ParseRepoTags(repoTags []string) []*Image {
|
||||
images := make([]*Image, 0)
|
||||
for _, repoTag := range repoTags {
|
||||
image := ParseRepoTag(repoTag)
|
||||
if image != nil {
|
||||
images = append(images, image)
|
||||
}
|
||||
}
|
||||
|
||||
return images
|
||||
}
|
||||
|
||||
func ParseRepoDigest(repoDigest string) digest.Digest {
|
||||
if !strings.ContainsAny(repoDigest, "@") {
|
||||
return ""
|
||||
}
|
||||
|
||||
d, err := digest.Parse(strings.Split(repoDigest, "@")[1])
|
||||
if err != nil {
|
||||
log.Warn().Msgf("Skip invalid repo digest item: %s [error: %v]", repoDigest, err)
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func ParseRepoTag(repoTag string) *Image {
|
||||
if repoTag == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
image, err := ParseImage(ParseImageOptions{
|
||||
Name: repoTag,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("repoTag", repoTag).Msg("RepoTag cannot be parsed.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return &image
|
||||
}
|
||||
|
||||
func (c *DigestClient) timeoutContext() (context.Context, context.CancelFunc) {
|
||||
ctx := context.Background()
|
||||
var cancel context.CancelFunc = func() {}
|
||||
|
||||
if c.opts.Timeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(ctx, c.opts.Timeout)
|
||||
}
|
||||
|
||||
return ctx, cancel
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue