mirror of
https://github.com/containers/podman.git
synced 2025-07-07 21:07:12 +08:00

Allow Podman containers to request to use a specific OCI runtime if multiple runtimes are configured. This is the first step to properly supporting containers in a multi-runtime environment. The biggest changes are that all OCI runtimes are now initialized when Podman creates its runtime, and containers now use the runtime requested in their configuration (instead of always the default runtime). Signed-off-by: Matthew Heon <matthew.heon@pm.me>
242 lines
6.8 KiB
Go
242 lines
6.8 KiB
Go
package libpod
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/containers/buildah"
|
|
"github.com/containers/buildah/util"
|
|
is "github.com/containers/image/storage"
|
|
"github.com/containers/libpod/libpod/events"
|
|
"github.com/containers/libpod/libpod/image"
|
|
"github.com/pkg/errors"
|
|
"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
|
|
IncludeVolumes bool
|
|
Author string
|
|
Message string
|
|
Changes []string
|
|
}
|
|
|
|
// ChangeCmds is the list of valid Changes commands to passed to the Commit call
|
|
var ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"}
|
|
|
|
// 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.config.Rootfs != "" {
|
|
return nil, errors.Errorf("cannot commit a container that uses an exploded rootfs")
|
|
}
|
|
|
|
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.ociRuntime.pauseContainer(c); err != nil {
|
|
return nil, errors.Wrapf(err, "error pausing container %q", c.ID())
|
|
}
|
|
defer func() {
|
|
if err := c.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.SplitN(e, "=", 2)
|
|
importBuilder.SetEnv(splitEnv[0], splitEnv[1])
|
|
}
|
|
}
|
|
// Expose ports
|
|
for _, p := range c.config.PortMappings {
|
|
importBuilder.SetPort(fmt.Sprintf("%d", p.ContainerPort))
|
|
}
|
|
// Labels
|
|
for k, v := range c.Labels() {
|
|
importBuilder.SetLabel(k, v)
|
|
}
|
|
// No stop signal
|
|
// User
|
|
importBuilder.SetUser(c.User())
|
|
// Volumes
|
|
if options.IncludeVolumes {
|
|
for _, v := range c.config.UserVolumes {
|
|
if v != "" {
|
|
importBuilder.AddVolume(v)
|
|
}
|
|
}
|
|
}
|
|
// Workdir
|
|
importBuilder.SetWorkDir(c.Spec().Process.Cwd)
|
|
|
|
genCmd := func(cmd string) []string {
|
|
trim := func(cmd []string) []string {
|
|
if len(cmd) == 0 {
|
|
return cmd
|
|
}
|
|
|
|
retCmd := []string{}
|
|
for _, c := range cmd {
|
|
if len(c) >= 2 {
|
|
if c[0] == '"' && c[len(c)-1] == '"' {
|
|
retCmd = append(retCmd, c[1:len(c)-1])
|
|
continue
|
|
}
|
|
}
|
|
retCmd = append(retCmd, c)
|
|
}
|
|
return retCmd
|
|
}
|
|
if strings.HasPrefix(cmd, "[") {
|
|
cmd = strings.TrimPrefix(cmd, "[")
|
|
cmd = strings.TrimSuffix(cmd, "]")
|
|
return trim(strings.Split(cmd, ","))
|
|
}
|
|
return []string{"/bin/sh", "-c", cmd}
|
|
}
|
|
// Process user changes
|
|
for _, change := range options.Changes {
|
|
splitChange := strings.SplitN(change, "=", 2)
|
|
if len(splitChange) != 2 {
|
|
splitChange = strings.SplitN(change, " ", 2)
|
|
if len(splitChange) < 2 {
|
|
return nil, errors.Errorf("invalid change %s format", change)
|
|
}
|
|
}
|
|
|
|
switch strings.ToUpper(splitChange[0]) {
|
|
case "CMD":
|
|
importBuilder.SetCmd(genCmd(splitChange[1]))
|
|
case "ENTRYPOINT":
|
|
importBuilder.SetEntrypoint(genCmd(splitChange[1]))
|
|
case "ENV":
|
|
change := strings.Split(splitChange[1], " ")
|
|
name := change[0]
|
|
val := ""
|
|
if len(change) < 2 {
|
|
change = strings.Split(change[0], "=")
|
|
}
|
|
if len(change) < 2 {
|
|
var ok bool
|
|
val, ok = os.LookupEnv(name)
|
|
if !ok {
|
|
return nil, errors.Errorf("invalid env variable %q: not defined in your environment", name)
|
|
}
|
|
} else {
|
|
val = strings.Join(change[1:], " ")
|
|
}
|
|
if !isEnvCleared { // Multiple values are valid, only clear once.
|
|
importBuilder.ClearEnv()
|
|
isEnvCleared = true
|
|
}
|
|
importBuilder.SetEnv(name, val)
|
|
case "EXPOSE":
|
|
if !isExposeCleared { // Multiple values are valid, only clear once
|
|
importBuilder.ClearPorts()
|
|
isExposeCleared = true
|
|
}
|
|
importBuilder.SetPort(splitChange[1])
|
|
case "LABEL":
|
|
change := strings.Split(splitChange[1], " ")
|
|
if len(change) < 2 {
|
|
change = strings.Split(change[0], "=")
|
|
}
|
|
if len(change) < 2 {
|
|
return nil, errors.Errorf("invalid label %s format, requires to NAME=VAL", splitChange[1])
|
|
}
|
|
if !isLabelCleared { // multiple values are valid, only clear once
|
|
importBuilder.ClearLabels()
|
|
isLabelCleared = true
|
|
}
|
|
importBuilder.SetLabel(change[0], strings.Join(change[1:], " "))
|
|
case "ONBUILD":
|
|
importBuilder.SetOnBuild(splitChange[1])
|
|
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, _, _, err := util.ResolveName(destImage, "", sc, c.runtime.store)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error resolving name %q", destImage)
|
|
}
|
|
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
|
|
}
|
|
defer c.newContainerEvent(events.Commit)
|
|
return c.runtime.imageRuntime.NewFromLocal(id)
|
|
}
|