mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00

implement a new command `podman generate spec` which can formulate a json specgen to be consumed by both the pod and container creation API. supported flags are --verbose (default true) print output to the terminal --compact print the json output in a single line format to be piped to the API --filename put the output in a file --clone rename the pod/ctr in the spec so it won't conflict w/ an existing entity Signed-off-by: Charlie Doern <cdoern@redhat.com>
354 lines
10 KiB
Go
354 lines
10 KiB
Go
package generate
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/containers/podman/v4/libpod"
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
|
"github.com/containers/podman/v4/pkg/specgen"
|
|
"github.com/containers/podman/v4/pkg/specgenutil"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
|
|
if err := p.PodSpecGen.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p.PodSpecGen.ResourceLimits == nil {
|
|
p.PodSpecGen.ResourceLimits = &specs.LinuxResources{}
|
|
}
|
|
|
|
if !p.PodSpecGen.NoInfra {
|
|
imageName, err := PullOrBuildInfraImage(rt, p.PodSpecGen.InfraImage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.PodSpecGen.InfraImage = imageName
|
|
p.PodSpecGen.InfraContainerSpec.RawImageName = imageName
|
|
}
|
|
|
|
if !p.PodSpecGen.NoInfra && p.PodSpecGen.InfraContainerSpec != nil {
|
|
var err error
|
|
p.PodSpecGen.InfraContainerSpec, err = MapSpec(&p.PodSpecGen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if !p.PodSpecGen.NoInfra {
|
|
err := FinishThrottleDevices(p.PodSpecGen.InfraContainerSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if p.PodSpecGen.InfraContainerSpec.ResourceLimits.BlockIO != nil {
|
|
p.PodSpecGen.ResourceLimits.BlockIO = p.PodSpecGen.InfraContainerSpec.ResourceLimits.BlockIO
|
|
}
|
|
|
|
weightDevices, err := WeightDevices(p.PodSpecGen.InfraContainerSpec.WeightDevice)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p.PodSpecGen.ResourceLimits != nil && len(weightDevices) > 0 {
|
|
if p.PodSpecGen.ResourceLimits.BlockIO == nil {
|
|
p.PodSpecGen.ResourceLimits.BlockIO = &specs.LinuxBlockIO{}
|
|
}
|
|
p.PodSpecGen.ResourceLimits.BlockIO.WeightDevice = weightDevices
|
|
}
|
|
}
|
|
|
|
options, err := createPodOptions(&p.PodSpecGen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pod, err := rt.NewPod(context.Background(), p.PodSpecGen, options...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !p.PodSpecGen.NoInfra && p.PodSpecGen.InfraContainerSpec != nil {
|
|
if p.PodSpecGen.InfraContainerSpec.Name == "" {
|
|
p.PodSpecGen.InfraContainerSpec.Name = pod.ID()[:12] + "-infra"
|
|
}
|
|
_, err = CompleteSpec(context.Background(), rt, p.PodSpecGen.InfraContainerSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.PodSpecGen.InfraContainerSpec.User = "" // infraSpec user will get incorrectly assigned via the container creation process, overwrite here
|
|
// infra's resource limits are used as a parsing tool,
|
|
// we do not want infra to get these resources in its cgroup
|
|
// make sure of that here.
|
|
p.PodSpecGen.InfraContainerSpec.ResourceLimits = nil
|
|
p.PodSpecGen.InfraContainerSpec.WeightDevice = nil
|
|
rtSpec, spec, opts, err := MakeContainer(context.Background(), rt, p.PodSpecGen.InfraContainerSpec, false, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
spec.Pod = pod.ID()
|
|
opts = append(opts, rt.WithPod(pod))
|
|
spec.CgroupParent = pod.CgroupParent()
|
|
infraCtr, err := ExecuteCreate(context.Background(), rt, rtSpec, spec, true, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pod, err = rt.AddInfra(context.Background(), pod, infraCtr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// SavePod is used to save the pod state and trigger a create event even if infra is not created
|
|
err := rt.SavePod(pod)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return pod, nil
|
|
}
|
|
|
|
func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, error) {
|
|
var (
|
|
options []libpod.PodCreateOption
|
|
)
|
|
if !p.NoInfra {
|
|
options = append(options, libpod.WithInfraContainer())
|
|
if p.ShareParent == nil || (p.ShareParent != nil && *p.ShareParent) {
|
|
options = append(options, libpod.WithPodParent())
|
|
}
|
|
nsOptions, err := GetNamespaceOptions(p.SharedNamespaces, p.InfraContainerSpec.NetNS.IsHost())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
options = append(options, nsOptions...)
|
|
// Use pod user and infra userns only when --userns is not set to host
|
|
if !p.InfraContainerSpec.UserNS.IsHost() && !p.InfraContainerSpec.UserNS.IsDefault() {
|
|
options = append(options, libpod.WithPodUser())
|
|
}
|
|
}
|
|
|
|
if len(p.ServiceContainerID) > 0 {
|
|
options = append(options, libpod.WithServiceContainer(p.ServiceContainerID))
|
|
}
|
|
|
|
if len(p.CgroupParent) > 0 {
|
|
options = append(options, libpod.WithPodCgroupParent(p.CgroupParent))
|
|
}
|
|
if len(p.Labels) > 0 {
|
|
options = append(options, libpod.WithPodLabels(p.Labels))
|
|
}
|
|
if len(p.Name) > 0 {
|
|
options = append(options, libpod.WithPodName(p.Name))
|
|
}
|
|
if p.PodCreateCommand != nil {
|
|
options = append(options, libpod.WithPodCreateCommand(p.PodCreateCommand))
|
|
}
|
|
|
|
if len(p.Hostname) > 0 {
|
|
options = append(options, libpod.WithPodHostname(p.Hostname))
|
|
}
|
|
|
|
if p.ResourceLimits != nil {
|
|
options = append(options, libpod.WithPodResources(*p.ResourceLimits))
|
|
}
|
|
|
|
options = append(options, libpod.WithPodExitPolicy(p.ExitPolicy))
|
|
|
|
return options, nil
|
|
}
|
|
|
|
// MapSpec modifies the already filled Infra specgenerator,
|
|
// replacing necessary values with those specified in pod creation
|
|
func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) {
|
|
if len(p.PortMappings) > 0 {
|
|
ports, err := ParsePortMapping(p.PortMappings, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.InfraContainerSpec.PortMappings = ports
|
|
}
|
|
switch p.NetNS.NSMode {
|
|
case specgen.Default, "":
|
|
if p.NoInfra {
|
|
logrus.Debugf("No networking because the infra container is missing")
|
|
break
|
|
}
|
|
case specgen.Bridge:
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.Bridge
|
|
logrus.Debugf("Pod using bridge network mode")
|
|
case specgen.Private:
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.Private
|
|
logrus.Debugf("Pod will use default network mode")
|
|
case specgen.Host:
|
|
logrus.Debugf("Pod will use host networking")
|
|
if len(p.InfraContainerSpec.PortMappings) > 0 ||
|
|
len(p.InfraContainerSpec.Networks) > 0 ||
|
|
p.InfraContainerSpec.NetNS.NSMode == specgen.NoNetwork {
|
|
return nil, fmt.Errorf("cannot set host network if network-related configuration is specified: %w", define.ErrInvalidArg)
|
|
}
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.Host
|
|
case specgen.Slirp:
|
|
logrus.Debugf("Pod will use slirp4netns")
|
|
if p.InfraContainerSpec.NetNS.NSMode != specgen.Host {
|
|
p.InfraContainerSpec.NetworkOptions = p.NetworkOptions
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.Slirp
|
|
}
|
|
case specgen.NoNetwork:
|
|
logrus.Debugf("Pod will not use networking")
|
|
if len(p.InfraContainerSpec.PortMappings) > 0 ||
|
|
len(p.InfraContainerSpec.Networks) > 0 ||
|
|
p.InfraContainerSpec.NetNS.NSMode == specgen.Host {
|
|
return nil, fmt.Errorf("cannot disable pod network if network-related configuration is specified: %w", define.ErrInvalidArg)
|
|
}
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.NoNetwork
|
|
default:
|
|
return nil, fmt.Errorf("pods presently do not support network mode %s", p.NetNS.NSMode)
|
|
}
|
|
|
|
if len(p.InfraCommand) > 0 {
|
|
p.InfraContainerSpec.Entrypoint = p.InfraCommand
|
|
}
|
|
|
|
if len(p.HostAdd) > 0 {
|
|
p.InfraContainerSpec.HostAdd = p.HostAdd
|
|
}
|
|
if len(p.DNSServer) > 0 {
|
|
var dnsServers []net.IP
|
|
dnsServers = append(dnsServers, p.DNSServer...)
|
|
|
|
p.InfraContainerSpec.DNSServers = dnsServers
|
|
}
|
|
if len(p.DNSOption) > 0 {
|
|
p.InfraContainerSpec.DNSOptions = p.DNSOption
|
|
}
|
|
if len(p.DNSSearch) > 0 {
|
|
p.InfraContainerSpec.DNSSearch = p.DNSSearch
|
|
}
|
|
if p.NoManageResolvConf {
|
|
p.InfraContainerSpec.UseImageResolvConf = true
|
|
}
|
|
if len(p.Networks) > 0 {
|
|
p.InfraContainerSpec.Networks = p.Networks
|
|
}
|
|
// deprecated cni networks for api users
|
|
if len(p.CNINetworks) > 0 {
|
|
p.InfraContainerSpec.CNINetworks = p.CNINetworks
|
|
}
|
|
if p.NoManageHosts {
|
|
p.InfraContainerSpec.UseImageHosts = p.NoManageHosts
|
|
}
|
|
|
|
if len(p.InfraConmonPidFile) > 0 {
|
|
p.InfraContainerSpec.ConmonPidFile = p.InfraConmonPidFile
|
|
}
|
|
|
|
p.InfraContainerSpec.Image = p.InfraImage
|
|
return p.InfraContainerSpec, nil
|
|
}
|
|
|
|
func PodConfigToSpec(rt *libpod.Runtime, spec *specgen.PodSpecGenerator, infraOptions *entities.ContainerCreateOptions, id string) (p *libpod.Pod, err error) {
|
|
pod, err := rt.LookupPod(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
infraSpec := &specgen.SpecGenerator{}
|
|
if pod.HasInfraContainer() {
|
|
infraID, err := pod.InfraContainerID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, _, err = ConfigToSpec(rt, infraSpec, infraID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
infraSpec.Hostname = ""
|
|
infraSpec.CgroupParent = ""
|
|
infraSpec.Pod = "" // remove old pod...
|
|
infraOptions.IsClone = true
|
|
infraOptions.IsInfra = true
|
|
|
|
n := infraSpec.Name
|
|
_, err = rt.LookupContainer(n + "-clone")
|
|
if err == nil { // if we found a ctr with this name, set it so the below switch can tell
|
|
n += "-clone"
|
|
}
|
|
|
|
switch {
|
|
case strings.Contains(n, "-clone"):
|
|
ind := strings.Index(n, "-clone") + 6
|
|
num, err := strconv.Atoi(n[ind:])
|
|
if num == 0 && err != nil { // clone1 is hard to get with this logic, just check for it here.
|
|
_, err = rt.LookupContainer(n + "1")
|
|
if err != nil {
|
|
infraSpec.Name = n + "1"
|
|
break
|
|
}
|
|
} else {
|
|
n = n[0:ind]
|
|
}
|
|
err = nil
|
|
count := num
|
|
for err == nil {
|
|
count++
|
|
tempN := n + strconv.Itoa(count)
|
|
_, err = rt.LookupContainer(tempN)
|
|
}
|
|
n += strconv.Itoa(count)
|
|
infraSpec.Name = n
|
|
default:
|
|
infraSpec.Name = n + "-clone"
|
|
}
|
|
|
|
err = specgenutil.FillOutSpecGen(infraSpec, infraOptions, []string{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out, err := CompleteSpec(context.Background(), rt, infraSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Print warnings
|
|
if len(out) > 0 {
|
|
for _, w := range out {
|
|
fmt.Println("Could not properly complete the spec as expected:")
|
|
fmt.Fprintf(os.Stderr, "%s\n", w)
|
|
}
|
|
}
|
|
|
|
spec.InfraContainerSpec = infraSpec
|
|
matching, err := json.Marshal(infraSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// track name before unmarshal so we do not overwrite w/ infra
|
|
name := spec.Name
|
|
err = json.Unmarshal(matching, spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
spec.Name = name
|
|
}
|
|
|
|
// need to reset hostname, name etc of both pod and infra
|
|
spec.Hostname = ""
|
|
|
|
if len(spec.InfraContainerSpec.Image) > 0 {
|
|
spec.InfraImage = spec.InfraContainerSpec.Image
|
|
}
|
|
return pod, nil
|
|
}
|