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