Files
podman/libpod/container_commit.go
Matthew Heon de13777e71 Make references to the Process part of Spec conditional
The OCI runtime spec does not require Process to be passed (IE,
it can be nil). Make most of our references to it conditional on
it existing.

Signed-off-by: Matthew Heon <mheon@redhat.com>

Closes: #828
Approved by: mheon
2018-05-25 15:32:47 +00:00

169 lines
4.8 KiB
Go

package libpod
import (
"context"
"strings"
is "github.com/containers/image/storage"
"github.com/pkg/errors"
"github.com/projectatomic/buildah"
"github.com/projectatomic/buildah/util"
"github.com/projectatomic/libpod/libpod/image"
"github.com/sirupsen/logrus"
)
// ContainerCommitOptions is a struct used to commit a container to an image
// It uses buildah's CommitOptions as a base. Long-term we might wish to
// add these to the buildah struct once buildah is more integrated with
//libpod
type ContainerCommitOptions struct {
buildah.CommitOptions
Pause bool
Author string
Message string
Changes []string
}
// Commit commits the changes between a container and its image, creating a new
// image
func (c *Container) Commit(ctx context.Context, destImage string, options ContainerCommitOptions) (*image.Image, error) {
var (
isEnvCleared, isLabelCleared, isExposeCleared, isVolumeCleared bool
)
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return nil, err
}
}
if c.state.State == ContainerStateRunning && options.Pause {
if err := c.runtime.ociRuntime.pauseContainer(c); err != nil {
return nil, errors.Wrapf(err, "error pausing container %q", c.ID())
}
defer func() {
if err := c.runtime.ociRuntime.unpauseContainer(c); err != nil {
logrus.Errorf("error unpausing container %q: %v", c.ID(), err)
}
}()
}
sc := image.GetSystemContext(options.SignaturePolicyPath, "", false)
builderOptions := buildah.ImportOptions{
Container: c.ID(),
SignaturePolicyPath: options.SignaturePolicyPath,
}
commitOptions := buildah.CommitOptions{
SignaturePolicyPath: options.SignaturePolicyPath,
ReportWriter: options.ReportWriter,
SystemContext: sc,
PreferredManifestType: options.PreferredManifestType,
}
importBuilder, err := buildah.ImportBuilder(ctx, c.runtime.store, builderOptions)
if err != nil {
return nil, err
}
if options.Author != "" {
importBuilder.SetMaintainer(options.Author)
}
if options.Message != "" {
importBuilder.SetComment(options.Message)
}
// We need to take meta we find in the current container and
// add it to the resulting image.
// Entrypoint - always set this first or cmd will get wiped out
importBuilder.SetEntrypoint(c.config.Entrypoint)
// Cmd
importBuilder.SetCmd(c.config.Command)
// Env
// TODO - this includes all the default environment vars as well
// Should we store the ENV we actually want in the spec separately?
if c.config.Spec.Process != nil {
for _, e := range c.config.Spec.Process.Env {
splitEnv := strings.Split(e, "=")
importBuilder.SetEnv(splitEnv[0], splitEnv[1])
}
}
// Expose ports
for _, p := range c.config.PortMappings {
importBuilder.SetPort(string(p.ContainerPort))
}
// Labels
for k, v := range c.Labels() {
importBuilder.SetLabel(k, v)
}
// No stop signal
// User
importBuilder.SetUser(c.User())
// Volumes
for _, v := range c.config.UserVolumes {
if v != "" {
importBuilder.AddVolume(v)
}
}
// Workdir
importBuilder.SetWorkDir(c.Spec().Process.Cwd)
// Process user changes
for _, change := range options.Changes {
splitChange := strings.Split(change, "=")
switch strings.ToUpper(splitChange[0]) {
case "CMD":
importBuilder.SetCmd(splitChange[1:])
case "ENTRYPOINT":
importBuilder.SetEntrypoint(splitChange[1:])
case "ENV":
if !isEnvCleared { // Multiple values are valid, only clear once.
importBuilder.ClearEnv()
isEnvCleared = true
}
importBuilder.SetEnv(splitChange[1], splitChange[2])
case "EXPOSE":
if !isExposeCleared { // Multiple values are valid, only clear once
importBuilder.ClearPorts()
isExposeCleared = true
}
importBuilder.SetPort(splitChange[1])
case "LABEL":
if !isLabelCleared { // multiple values are valid, only clear once
importBuilder.ClearLabels()
isLabelCleared = true
}
importBuilder.SetLabel(splitChange[1], splitChange[2])
case "STOPSIGNAL":
// No Set StopSignal
case "USER":
importBuilder.SetUser(splitChange[1])
case "VOLUME":
if !isVolumeCleared { // multiple values are valid, only clear once
importBuilder.ClearVolumes()
isVolumeCleared = true
}
importBuilder.AddVolume(splitChange[1])
case "WORKDIR":
importBuilder.SetWorkDir(splitChange[1])
}
}
candidates := util.ResolveName(destImage, "", sc, c.runtime.store)
if len(candidates) == 0 {
return nil, errors.Errorf("error parsing target image name %q", destImage)
}
imageRef, err := is.Transport.ParseStoreReference(c.runtime.store, candidates[0])
if err != nil {
return nil, errors.Wrapf(err, "error parsing target image name %q", destImage)
}
id, err := importBuilder.Commit(ctx, imageRef, commitOptions)
if err != nil {
return nil, err
}
return c.runtime.imageRuntime.NewFromLocal(id)
}