github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/daemon/commit.go (about) 1 package daemon 2 3 import ( 4 "encoding/json" 5 "io" 6 "runtime" 7 "strings" 8 "time" 9 10 "github.com/docker/distribution/reference" 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/pkg/errors" 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.StartPeriod == 0 { 98 userConf.Healthcheck.StartPeriod = imageConf.Healthcheck.StartPeriod 99 } 100 if userConf.Healthcheck.Retries == 0 { 101 userConf.Healthcheck.Retries = imageConf.Healthcheck.Retries 102 } 103 } 104 } 105 106 if userConf.WorkingDir == "" { 107 userConf.WorkingDir = imageConf.WorkingDir 108 } 109 if len(userConf.Volumes) == 0 { 110 userConf.Volumes = imageConf.Volumes 111 } else { 112 for k, v := range imageConf.Volumes { 113 userConf.Volumes[k] = v 114 } 115 } 116 117 if userConf.StopSignal == "" { 118 userConf.StopSignal = imageConf.StopSignal 119 } 120 return nil 121 } 122 123 // Commit creates a new filesystem image from the current state of a container. 124 // The image can optionally be tagged into a repository. 125 func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (string, error) { 126 start := time.Now() 127 container, err := daemon.GetContainer(name) 128 if err != nil { 129 return "", err 130 } 131 132 containerConfig := c.ContainerConfig 133 if containerConfig == nil { 134 containerConfig = container.Config 135 } 136 137 // It is not possible to commit a running container on Windows and on Solaris. 138 if (runtime.GOOS == "windows" || runtime.GOOS == "solaris") && container.IsRunning() { 139 return "", errors.Errorf("%+v does not support commit of a running container", runtime.GOOS) 140 } 141 142 if c.Pause && !container.IsPaused() { 143 daemon.containerPause(container) 144 defer daemon.containerUnpause(container) 145 } 146 147 newConfig, err := dockerfile.BuildFromConfig(c.Config, c.Changes) 148 if err != nil { 149 return "", err 150 } 151 152 if c.MergeConfigs { 153 if err := merge(newConfig, container.Config); err != nil { 154 return "", err 155 } 156 } 157 158 rwTar, err := daemon.exportContainerRw(container) 159 if err != nil { 160 return "", err 161 } 162 defer func() { 163 if rwTar != nil { 164 rwTar.Close() 165 } 166 }() 167 168 var history []image.History 169 rootFS := image.NewRootFS() 170 osVersion := "" 171 var osFeatures []string 172 173 if container.ImageID != "" { 174 img, err := daemon.imageStore.Get(container.ImageID) 175 if err != nil { 176 return "", err 177 } 178 history = img.History 179 rootFS = img.RootFS 180 osVersion = img.OSVersion 181 osFeatures = img.OSFeatures 182 } 183 184 l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID()) 185 if err != nil { 186 return "", err 187 } 188 defer layer.ReleaseAndLog(daemon.layerStore, l) 189 190 h := image.History{ 191 Author: c.Author, 192 Created: time.Now().UTC(), 193 CreatedBy: strings.Join(containerConfig.Cmd, " "), 194 Comment: c.Comment, 195 EmptyLayer: true, 196 } 197 198 if diffID := l.DiffID(); layer.DigestSHA256EmptyTar != diffID { 199 h.EmptyLayer = false 200 rootFS.Append(diffID) 201 } 202 203 history = append(history, h) 204 205 config, err := json.Marshal(&image.Image{ 206 V1Image: image.V1Image{ 207 DockerVersion: dockerversion.Version, 208 Config: newConfig, 209 Architecture: runtime.GOARCH, 210 OS: runtime.GOOS, 211 Container: container.ID, 212 ContainerConfig: *containerConfig, 213 Author: c.Author, 214 Created: h.Created, 215 }, 216 RootFS: rootFS, 217 History: history, 218 OSFeatures: osFeatures, 219 OSVersion: osVersion, 220 }) 221 222 if err != nil { 223 return "", err 224 } 225 226 id, err := daemon.imageStore.Create(config) 227 if err != nil { 228 return "", err 229 } 230 231 if container.ImageID != "" { 232 if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil { 233 return "", err 234 } 235 } 236 237 imageRef := "" 238 if c.Repo != "" { 239 newTag, err := reference.ParseNormalizedNamed(c.Repo) // todo: should move this to API layer 240 if err != nil { 241 return "", err 242 } 243 if !reference.IsNameOnly(newTag) { 244 return "", errors.Errorf("unexpected repository name: %s", c.Repo) 245 } 246 if c.Tag != "" { 247 if newTag, err = reference.WithTag(newTag, c.Tag); err != nil { 248 return "", err 249 } 250 } 251 if err := daemon.TagImageWithReference(id, newTag); err != nil { 252 return "", err 253 } 254 imageRef = reference.FamiliarString(newTag) 255 } 256 257 attributes := map[string]string{ 258 "comment": c.Comment, 259 "imageID": id.String(), 260 "imageRef": imageRef, 261 } 262 daemon.LogContainerEventWithAttributes(container, "commit", attributes) 263 containerActions.WithValues("commit").UpdateSince(start) 264 return id.String(), nil 265 } 266 267 func (daemon *Daemon) exportContainerRw(container *container.Container) (io.ReadCloser, error) { 268 if err := daemon.Mount(container); err != nil { 269 return nil, err 270 } 271 272 archive, err := container.RWLayer.TarStream() 273 if err != nil { 274 daemon.Unmount(container) // logging is already handled in the `Unmount` function 275 return nil, err 276 } 277 return ioutils.NewReadCloserWrapper(archive, func() error { 278 archive.Close() 279 return container.RWLayer.Unmount() 280 }), 281 nil 282 }