github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/commit.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "strings" 8 "time" 9 10 "github.com/distribution/reference" 11 "github.com/docker/docker/api/types/backend" 12 containertypes "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/api/types/events" 14 "github.com/docker/docker/builder/dockerfile" 15 "github.com/docker/docker/errdefs" 16 "github.com/pkg/errors" 17 ) 18 19 // merge merges two Config, the image container configuration (defaults values), 20 // and the user container configuration, either passed by the API or generated 21 // by the cli. 22 // It will mutate the specified user configuration (userConf) with the image 23 // configuration where the user configuration is incomplete. 24 func merge(userConf, imageConf *containertypes.Config) error { 25 if userConf.User == "" { 26 userConf.User = imageConf.User 27 } 28 if len(userConf.ExposedPorts) == 0 { 29 userConf.ExposedPorts = imageConf.ExposedPorts 30 } else if imageConf.ExposedPorts != nil { 31 for port := range imageConf.ExposedPorts { 32 if _, exists := userConf.ExposedPorts[port]; !exists { 33 userConf.ExposedPorts[port] = struct{}{} 34 } 35 } 36 } 37 38 if len(userConf.Env) == 0 { 39 userConf.Env = imageConf.Env 40 } else { 41 for _, imageEnv := range imageConf.Env { 42 found := false 43 imageEnvKey, _, _ := strings.Cut(imageEnv, "=") 44 for _, userEnv := range userConf.Env { 45 userEnvKey, _, _ := strings.Cut(userEnv, "=") 46 if isWindows { 47 // Case insensitive environment variables on Windows 48 found = strings.EqualFold(imageEnvKey, userEnvKey) 49 } else { 50 found = imageEnvKey == userEnvKey 51 } 52 if found { 53 break 54 } 55 } 56 if !found { 57 userConf.Env = append(userConf.Env, imageEnv) 58 } 59 } 60 } 61 62 if userConf.Labels == nil { 63 userConf.Labels = map[string]string{} 64 } 65 for l, v := range imageConf.Labels { 66 if _, ok := userConf.Labels[l]; !ok { 67 userConf.Labels[l] = v 68 } 69 } 70 71 if len(userConf.Entrypoint) == 0 { 72 if len(userConf.Cmd) == 0 { 73 userConf.Cmd = imageConf.Cmd 74 } 75 76 if userConf.Entrypoint == nil { 77 userConf.Entrypoint = imageConf.Entrypoint 78 } 79 } 80 if imageConf.Healthcheck != nil { 81 if userConf.Healthcheck == nil { 82 userConf.Healthcheck = imageConf.Healthcheck 83 } else { 84 if len(userConf.Healthcheck.Test) == 0 { 85 userConf.Healthcheck.Test = imageConf.Healthcheck.Test 86 } 87 if userConf.Healthcheck.Interval == 0 { 88 userConf.Healthcheck.Interval = imageConf.Healthcheck.Interval 89 } 90 if userConf.Healthcheck.Timeout == 0 { 91 userConf.Healthcheck.Timeout = imageConf.Healthcheck.Timeout 92 } 93 if userConf.Healthcheck.StartPeriod == 0 { 94 userConf.Healthcheck.StartPeriod = imageConf.Healthcheck.StartPeriod 95 } 96 if userConf.Healthcheck.StartInterval == 0 { 97 userConf.Healthcheck.StartInterval = imageConf.Healthcheck.StartInterval 98 } 99 if userConf.Healthcheck.Retries == 0 { 100 userConf.Healthcheck.Retries = imageConf.Healthcheck.Retries 101 } 102 } 103 } 104 105 if userConf.WorkingDir == "" { 106 userConf.WorkingDir = imageConf.WorkingDir 107 } 108 if len(userConf.Volumes) == 0 { 109 userConf.Volumes = imageConf.Volumes 110 } else { 111 for k, v := range imageConf.Volumes { 112 userConf.Volumes[k] = v 113 } 114 } 115 116 if userConf.StopSignal == "" { 117 userConf.StopSignal = imageConf.StopSignal 118 } 119 return nil 120 } 121 122 // CreateImageFromContainer creates a new image from a container. The container 123 // config will be updated by applying the change set to the custom config, then 124 // applying that config over the existing container config. 125 func (daemon *Daemon) CreateImageFromContainer(ctx context.Context, name string, c *backend.CreateImageConfig) (string, error) { 126 start := time.Now() 127 128 container, err := daemon.GetContainer(name) 129 if err != nil { 130 return "", err 131 } 132 133 // It is not possible to commit a running container on Windows 134 if isWindows && container.IsRunning() { 135 return "", errors.Errorf("%+v does not support commit of a running container", runtime.GOOS) 136 } 137 138 if container.IsDead() { 139 return "", errdefs.Conflict(fmt.Errorf("You cannot commit container %s which is Dead", container.ID)) 140 } 141 142 if container.IsRemovalInProgress() { 143 return "", errdefs.Conflict(fmt.Errorf("You cannot commit container %s which is being removed", container.ID)) 144 } 145 146 if c.Pause && !container.IsPaused() { 147 daemon.containerPause(container) 148 defer daemon.containerUnpause(container) 149 } 150 151 if c.Config == nil { 152 c.Config = container.Config 153 } 154 newConfig, err := dockerfile.BuildFromConfig(ctx, c.Config, c.Changes, container.OS) 155 if err != nil { 156 return "", err 157 } 158 if err := merge(newConfig, container.Config); err != nil { 159 return "", err 160 } 161 162 id, err := daemon.imageService.CommitImage(ctx, backend.CommitConfig{ 163 Author: c.Author, 164 Comment: c.Comment, 165 Config: newConfig, 166 ContainerConfig: container.Config, 167 ContainerID: container.ID, 168 ContainerMountLabel: container.MountLabel, 169 ContainerOS: container.OS, 170 ParentImageID: string(container.ImageID), 171 }) 172 if err != nil { 173 return "", err 174 } 175 176 imageRef := "" 177 if c.Tag != nil { 178 err = daemon.imageService.TagImage(ctx, id, c.Tag) 179 if err != nil { 180 return "", err 181 } 182 imageRef = reference.FamiliarString(c.Tag) 183 } 184 daemon.LogContainerEventWithAttributes(container, events.ActionCommit, map[string]string{ 185 "comment": c.Comment, 186 "imageID": id.String(), 187 "imageRef": imageRef, 188 }) 189 containerActions.WithValues("commit").UpdateSince(start) 190 return id.String(), nil 191 }