github.com/jen20/docker@v1.13.1/daemon/commit.go (about) 1 package daemon 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "runtime" 8 "strings" 9 "time" 10 11 "github.com/docker/docker/api/types/backend" 12 containertypes "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/builder/dockerfile" 14 "github.com/docker/docker/container" 15 "github.com/docker/docker/dockerversion" 16 "github.com/docker/docker/image" 17 "github.com/docker/docker/layer" 18 "github.com/docker/docker/pkg/ioutils" 19 "github.com/docker/docker/reference" 20 ) 21 22 // merge merges two Config, the image container configuration (defaults values), 23 // and the user container configuration, either passed by the API or generated 24 // by the cli. 25 // It will mutate the specified user configuration (userConf) with the image 26 // configuration where the user configuration is incomplete. 27 func merge(userConf, imageConf *containertypes.Config) error { 28 if userConf.User == "" { 29 userConf.User = imageConf.User 30 } 31 if len(userConf.ExposedPorts) == 0 { 32 userConf.ExposedPorts = imageConf.ExposedPorts 33 } else if imageConf.ExposedPorts != nil { 34 for port := range imageConf.ExposedPorts { 35 if _, exists := userConf.ExposedPorts[port]; !exists { 36 userConf.ExposedPorts[port] = struct{}{} 37 } 38 } 39 } 40 41 if len(userConf.Env) == 0 { 42 userConf.Env = imageConf.Env 43 } else { 44 for _, imageEnv := range imageConf.Env { 45 found := false 46 imageEnvKey := strings.Split(imageEnv, "=")[0] 47 for _, userEnv := range userConf.Env { 48 userEnvKey := strings.Split(userEnv, "=")[0] 49 if runtime.GOOS == "windows" { 50 // Case insensitive environment variables on Windows 51 imageEnvKey = strings.ToUpper(imageEnvKey) 52 userEnvKey = strings.ToUpper(userEnvKey) 53 } 54 if imageEnvKey == userEnvKey { 55 found = true 56 break 57 } 58 } 59 if !found { 60 userConf.Env = append(userConf.Env, imageEnv) 61 } 62 } 63 } 64 65 if userConf.Labels == nil { 66 userConf.Labels = map[string]string{} 67 } 68 for l, v := range imageConf.Labels { 69 if _, ok := userConf.Labels[l]; !ok { 70 userConf.Labels[l] = v 71 } 72 } 73 74 if len(userConf.Entrypoint) == 0 { 75 if len(userConf.Cmd) == 0 { 76 userConf.Cmd = imageConf.Cmd 77 userConf.ArgsEscaped = imageConf.ArgsEscaped 78 } 79 80 if userConf.Entrypoint == nil { 81 userConf.Entrypoint = imageConf.Entrypoint 82 } 83 } 84 if imageConf.Healthcheck != nil { 85 if userConf.Healthcheck == nil { 86 userConf.Healthcheck = imageConf.Healthcheck 87 } else { 88 if len(userConf.Healthcheck.Test) == 0 { 89 userConf.Healthcheck.Test = imageConf.Healthcheck.Test 90 } 91 if userConf.Healthcheck.Interval == 0 { 92 userConf.Healthcheck.Interval = imageConf.Healthcheck.Interval 93 } 94 if userConf.Healthcheck.Timeout == 0 { 95 userConf.Healthcheck.Timeout = imageConf.Healthcheck.Timeout 96 } 97 if userConf.Healthcheck.Retries == 0 { 98 userConf.Healthcheck.Retries = imageConf.Healthcheck.Retries 99 } 100 } 101 } 102 103 if userConf.WorkingDir == "" { 104 userConf.WorkingDir = imageConf.WorkingDir 105 } 106 if len(userConf.Volumes) == 0 { 107 userConf.Volumes = imageConf.Volumes 108 } else { 109 for k, v := range imageConf.Volumes { 110 userConf.Volumes[k] = v 111 } 112 } 113 114 if userConf.StopSignal == "" { 115 userConf.StopSignal = imageConf.StopSignal 116 } 117 return nil 118 } 119 120 // Commit creates a new filesystem image from the current state of a container. 121 // The image can optionally be tagged into a repository. 122 func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (string, error) { 123 start := time.Now() 124 container, err := daemon.GetContainer(name) 125 if err != nil { 126 return "", err 127 } 128 129 // It is not possible to commit a running container on Windows and on Solaris. 130 if (runtime.GOOS == "windows" || runtime.GOOS == "solaris") && container.IsRunning() { 131 return "", fmt.Errorf("%+v does not support commit of a running container", runtime.GOOS) 132 } 133 134 if c.Pause && !container.IsPaused() { 135 daemon.containerPause(container) 136 defer daemon.containerUnpause(container) 137 } 138 139 newConfig, err := dockerfile.BuildFromConfig(c.Config, c.Changes) 140 if err != nil { 141 return "", err 142 } 143 144 if c.MergeConfigs { 145 if err := merge(newConfig, container.Config); err != nil { 146 return "", err 147 } 148 } 149 150 rwTar, err := daemon.exportContainerRw(container) 151 if err != nil { 152 return "", err 153 } 154 defer func() { 155 if rwTar != nil { 156 rwTar.Close() 157 } 158 }() 159 160 var history []image.History 161 rootFS := image.NewRootFS() 162 osVersion := "" 163 var osFeatures []string 164 165 if container.ImageID != "" { 166 img, err := daemon.imageStore.Get(container.ImageID) 167 if err != nil { 168 return "", err 169 } 170 history = img.History 171 rootFS = img.RootFS 172 osVersion = img.OSVersion 173 osFeatures = img.OSFeatures 174 } 175 176 l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID()) 177 if err != nil { 178 return "", err 179 } 180 defer layer.ReleaseAndLog(daemon.layerStore, l) 181 182 h := image.History{ 183 Author: c.Author, 184 Created: time.Now().UTC(), 185 CreatedBy: strings.Join(container.Config.Cmd, " "), 186 Comment: c.Comment, 187 EmptyLayer: true, 188 } 189 190 if diffID := l.DiffID(); layer.DigestSHA256EmptyTar != diffID { 191 h.EmptyLayer = false 192 rootFS.Append(diffID) 193 } 194 195 history = append(history, h) 196 197 config, err := json.Marshal(&image.Image{ 198 V1Image: image.V1Image{ 199 DockerVersion: dockerversion.Version, 200 Config: newConfig, 201 Architecture: runtime.GOARCH, 202 OS: runtime.GOOS, 203 Container: container.ID, 204 ContainerConfig: *container.Config, 205 Author: c.Author, 206 Created: h.Created, 207 }, 208 RootFS: rootFS, 209 History: history, 210 OSFeatures: osFeatures, 211 OSVersion: osVersion, 212 }) 213 214 if err != nil { 215 return "", err 216 } 217 218 id, err := daemon.imageStore.Create(config) 219 if err != nil { 220 return "", err 221 } 222 223 if container.ImageID != "" { 224 if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil { 225 return "", err 226 } 227 } 228 229 imageRef := "" 230 if c.Repo != "" { 231 newTag, err := reference.WithName(c.Repo) // todo: should move this to API layer 232 if err != nil { 233 return "", err 234 } 235 if c.Tag != "" { 236 if newTag, err = reference.WithTag(newTag, c.Tag); err != nil { 237 return "", err 238 } 239 } 240 if err := daemon.TagImageWithReference(id, newTag); err != nil { 241 return "", err 242 } 243 imageRef = newTag.String() 244 } 245 246 attributes := map[string]string{ 247 "comment": c.Comment, 248 "imageID": id.String(), 249 "imageRef": imageRef, 250 } 251 daemon.LogContainerEventWithAttributes(container, "commit", attributes) 252 containerActions.WithValues("commit").UpdateSince(start) 253 return id.String(), nil 254 } 255 256 func (daemon *Daemon) exportContainerRw(container *container.Container) (io.ReadCloser, error) { 257 if err := daemon.Mount(container); err != nil { 258 return nil, err 259 } 260 261 archive, err := container.RWLayer.TarStream() 262 if err != nil { 263 daemon.Unmount(container) // logging is already handled in the `Unmount` function 264 return nil, err 265 } 266 return ioutils.NewReadCloserWrapper(archive, func() error { 267 archive.Close() 268 return container.RWLayer.Unmount() 269 }), 270 nil 271 }