github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/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 "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 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 130 if runtime.GOOS == "windows" && container.IsRunning() { 131 return "", fmt.Errorf("Windows does not support commit of a running container") 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 if c.Repo != "" { 230 newTag, err := reference.WithName(c.Repo) // todo: should move this to API layer 231 if err != nil { 232 return "", err 233 } 234 if c.Tag != "" { 235 if newTag, err = reference.WithTag(newTag, c.Tag); err != nil { 236 return "", err 237 } 238 } 239 if err := daemon.TagImageWithReference(id, newTag); err != nil { 240 return "", err 241 } 242 } 243 244 attributes := map[string]string{ 245 "comment": c.Comment, 246 } 247 daemon.LogContainerEventWithAttributes(container, "commit", attributes) 248 containerActions.WithValues("commit").UpdateSince(start) 249 return id.String(), nil 250 } 251 252 func (daemon *Daemon) exportContainerRw(container *container.Container) (io.ReadCloser, error) { 253 if err := daemon.Mount(container); err != nil { 254 return nil, err 255 } 256 257 archive, err := container.RWLayer.TarStream() 258 if err != nil { 259 daemon.Unmount(container) // logging is already handled in the `Unmount` function 260 return nil, err 261 } 262 return ioutils.NewReadCloserWrapper(archive, func() error { 263 archive.Close() 264 return container.RWLayer.Unmount() 265 }), 266 nil 267 }