Merge pull request #5611 from baude/v2podcreate

podmanv2 pod create using podspecgen
This commit is contained in:
OpenShift Merge Robot 2020-03-27 16:25:30 +01:00 committed by GitHub
commit 4233250c43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 666 additions and 116 deletions

2
cmd/podmanV2/Makefile Normal file
View File

@ -0,0 +1,2 @@
all:
GO111MODULE=off go build -tags 'ABISupport'

View File

@ -0,0 +1,108 @@
package common
import (
"net"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func getDefaultNetwork() string {
if rootless.IsRootless() {
return "slirp4netns"
}
return "bridge"
}
func GetNetFlags() *pflag.FlagSet {
netFlags := pflag.FlagSet{}
netFlags.StringSlice(
"add-host", []string{},
"Add a custom host-to-IP mapping (host:ip) (default [])",
)
netFlags.StringSlice(
"dns", []string{},
"Set custom DNS servers",
)
netFlags.StringSlice(
"dns-opt", []string{},
"Set custom DNS options",
)
netFlags.StringSlice(
"dns-search", []string{},
"Set custom DNS search domains",
)
netFlags.String(
"ip", "",
"Specify a static IPv4 address for the container",
)
netFlags.String(
"mac-address", "",
"Container MAC address (e.g. 92:d0:c6:0a:29:33)",
)
netFlags.String(
"network", getDefaultNetwork(),
"Connect a container to a network",
)
netFlags.StringSliceP(
"publish", "p", []string{},
"Publish a container's port, or a range of ports, to the host (default [])",
)
netFlags.Bool(
"no-hosts", false,
"Do not create /etc/hosts within the container, instead use the version from the image",
)
return &netFlags
}
func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
var (
err error
)
opts := entities.NetOptions{}
opts.AddHosts, err = cmd.Flags().GetStringSlice("add-host")
if err != nil {
return nil, err
}
servers, err := cmd.Flags().GetStringSlice("dns")
if err != nil {
return nil, err
}
for _, d := range servers {
if d == "none" {
opts.DNSHost = true
break
}
opts.DNSServers = append(opts.DNSServers, net.ParseIP(d))
}
opts.DNSSearch, err = cmd.Flags().GetStringSlice("dns-search")
if err != nil {
return nil, err
}
m, err := cmd.Flags().GetString("mac-address")
if err != nil {
return nil, err
}
if len(m) > 0 {
mac, err := net.ParseMAC(m)
if err != nil {
return nil, err
}
opts.StaticMAC = &mac
}
inputPorts, err := cmd.Flags().GetStringSlice("publish")
if err != nil {
return nil, err
}
if len(inputPorts) > 0 {
opts.PublishPorts, err = createPortBindings(inputPorts)
if err != nil {
return nil, err
}
}
opts.NoHosts, err = cmd.Flags().GetBool("no-hosts")
return &opts, err
}

View File

@ -0,0 +1,3 @@
package common
var DefaultKernelNamespaces = "cgroup,ipc,net,uts"

View File

@ -0,0 +1,43 @@
package common
import (
"strconv"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
)
// createPortBindings iterates ports mappings and exposed ports into a format CNI understands
func createPortBindings(ports []string) ([]ocicni.PortMapping, error) {
// TODO wants someone to rewrite this code in the future
var portBindings []ocicni.PortMapping
// The conversion from []string to natBindings is temporary while mheon reworks the port
// deduplication code. Eventually that step will not be required.
_, natBindings, err := nat.ParsePortSpecs(ports)
if err != nil {
return nil, err
}
for containerPb, hostPb := range natBindings {
var pm ocicni.PortMapping
pm.ContainerPort = int32(containerPb.Int())
for _, i := range hostPb {
var hostPort int
var err error
pm.HostIP = i.HostIP
if i.HostPort == "" {
hostPort = containerPb.Int()
} else {
hostPort, err = strconv.Atoi(i.HostPort)
if err != nil {
return nil, errors.Wrapf(err, "unable to convert host port to integer")
}
}
pm.HostPort = int32(hostPort)
pm.Protocol = containerPb.Proto()
portBindings = append(portBindings, pm)
}
}
return portBindings, nil
}

132
cmd/podmanV2/pods/create.go Normal file
View File

@ -0,0 +1,132 @@
package pods
import (
"context"
"fmt"
"os"
"strings"
"github.com/containers/libpod/cmd/podmanV2/common"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
podCreateDescription = `After creating the pod, the pod ID is printed to stdout.
You can then start it at any time with the podman pod start <pod_id> command. The pod will be created with the initial state 'created'.`
createCommand = &cobra.Command{
Use: "create",
Args: cobra.NoArgs,
Short: "Create a new empty pod",
Long: podCreateDescription,
RunE: create,
}
)
var (
createOptions entities.PodCreateOptions
labels, labelFile []string
podIDFile string
share string
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: createCommand,
Parent: podCmd,
})
flags := createCommand.Flags()
flags.SetInterspersed(false)
flags.AddFlagSet(common.GetNetFlags())
flags.StringVar(&createOptions.CGroupParent, "cgroup-parent", "", "Set parent cgroup for the pod")
flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with")
flags.StringVar(&createOptions.InfraImage, "infra-image", define.DefaultInfraImage, "The image of the infra container to associate with the pod")
flags.StringVar(&createOptions.InfraCommand, "infra-command", define.DefaultInfraCommand, "The command to run on the infra container when the pod is started")
flags.StringSliceVar(&labelFile, "label-file", []string{}, "Read in a line delimited file of labels")
flags.StringSliceVarP(&labels, "label", "l", []string{}, "Set metadata on pod (default [])")
flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod")
flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod")
flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file")
flags.StringVar(&share, "share", common.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share")
}
func create(cmd *cobra.Command, args []string) error {
var (
err error
podIdFile *os.File
)
createOptions.Labels, err = parse.GetAllLabels(labelFile, labels)
if err != nil {
return errors.Wrapf(err, "unable to process labels")
}
if !createOptions.Infra && cmd.Flag("share").Changed && share != "none" && share != "" {
return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container")
}
createOptions.Share = strings.Split(share, ",")
if cmd.Flag("pod-id-file").Changed {
podIdFile, err = util.OpenExclusiveFile(podIDFile)
if err != nil && os.IsExist(err) {
return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", podIDFile)
}
if err != nil {
return errors.Errorf("error opening pod-id-file %s", podIDFile)
}
defer errorhandling.CloseQuiet(podIdFile)
defer errorhandling.SyncQuiet(podIdFile)
}
createOptions.Net, err = common.NetFlagsToNetOptions(cmd)
if err != nil {
return err
}
netInput, err := cmd.Flags().GetString("network")
if err != nil {
return err
}
n := specgen.Namespace{}
switch netInput {
case "bridge":
n.NSMode = specgen.Bridge
case "host":
n.NSMode = specgen.Host
case "slip4netns":
n.NSMode = specgen.Slirp
default:
if strings.HasPrefix(netInput, "container:") { //nolint
split := strings.Split(netInput, ":")
if len(split) != 2 {
return errors.Errorf("invalid network paramater: %q", netInput)
}
n.NSMode = specgen.FromContainer
n.Value = split[1]
} else if strings.HasPrefix(netInput, "ns:") {
return errors.New("the ns: network option is not supported for pods")
} else {
n.NSMode = specgen.Bridge
createOptions.Net.CNINetworks = strings.Split(netInput, ",")
}
}
if len(createOptions.Net.PublishPorts) > 0 {
if !createOptions.Infra {
return errors.Errorf("you must have an infra container to publish port bindings to the host")
}
}
response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions)
if err != nil {
return err
}
fmt.Println(response.Id)
return nil
}

View File

@ -5,7 +5,6 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
)
@ -130,10 +129,8 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
}
// PruneVolumes removes unused volumes from the system
func (r *Runtime) PruneVolumes(ctx context.Context) ([]*entities.VolumePruneReport, error) {
var (
reports []*entities.VolumePruneReport
)
func (r *Runtime) PruneVolumes(ctx context.Context) (map[string]error, error) {
reports := make(map[string]error)
vols, err := r.GetAllVolumes()
if err != nil {
return nil, err
@ -142,12 +139,12 @@ func (r *Runtime) PruneVolumes(ctx context.Context) ([]*entities.VolumePruneRepo
for _, vol := range vols {
if err := r.RemoveVolume(ctx, vol, false); err != nil {
if errors.Cause(err) != define.ErrVolumeBeingUsed && errors.Cause(err) != define.ErrVolumeRemoved {
reports = append(reports, &entities.VolumePruneReport{Id: vol.Name(), Err: err})
reports[vol.Name()] = err
}
continue
}
vol.newVolumeEvent(events.Prune)
reports = append(reports, &entities.VolumePruneReport{Id: vol.Name()})
reports[vol.Name()] = nil
}
return reports, nil
}

View File

@ -356,11 +356,11 @@ func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) {
errs = append(errs, err)
return vids, errs
}
for _, r := range reports {
if r.Err == nil {
vids = append(vids, r.Id)
for k, v := range reports {
if v == nil {
vids = append(vids, k)
} else {
errs = append(errs, r.Err)
errs = append(errs, v)
}
}
return vids, errs

View File

@ -4,15 +4,13 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/shared/parse"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@ -21,76 +19,14 @@ import (
func PodCreate(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
options []libpod.PodCreateOption
err error
)
labels := make(map[string]string)
input := handlers.PodCreateConfig{}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
var psg specgen.PodSpecGenerator
if err := json.NewDecoder(r.Body).Decode(&psg); err != nil {
utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
return
}
if len(input.InfraCommand) > 0 || len(input.InfraImage) > 0 {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError,
errors.New("infra-command and infra-image are not implemented yet"))
return
}
// TODO long term we should break the following out of adapter and into libpod proper
// so that the cli and api can share the creation of a pod with the same options
if len(input.CGroupParent) > 0 {
options = append(options, libpod.WithPodCgroupParent(input.CGroupParent))
}
if len(input.Labels) > 0 {
labels, err = parse.GetAllLabels([]string{}, input.Labels)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
}
if len(labels) != 0 {
options = append(options, libpod.WithPodLabels(labels))
}
if len(input.Name) > 0 {
options = append(options, libpod.WithPodName(input.Name))
}
if len(input.Hostname) > 0 {
options = append(options, libpod.WithPodHostname(input.Hostname))
}
if input.Infra {
// TODO infra-image and infra-command are not supported in the libpod API yet. Will fix
// when implemented in libpod
options = append(options, libpod.WithInfraContainer())
sharedNamespaces := shared.DefaultKernelNamespaces
if len(input.Share) > 0 {
sharedNamespaces = input.Share
}
nsOptions, err := shared.GetNamespaceOptions(strings.Split(sharedNamespaces, ","))
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
options = append(options, nsOptions...)
}
if len(input.Publish) > 0 {
portBindings, err := shared.CreatePortBindings(input.Publish)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
options = append(options, libpod.WithInfraContainerPorts(portBindings))
}
// always have containers use pod cgroups
// User Opt out is not yet supported
options = append(options, libpod.WithPodCgroups())
pod, err := runtime.NewPod(r.Context(), options...)
pod, err := psg.MakePod(runtime)
if err != nil {
http_code := http.StatusInternalServerError
if errors.Cause(err) == define.ErrPodExists {

View File

@ -149,13 +149,20 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) {
func PruneVolumes(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
reports []*entities.VolumePruneReport
)
pruned, err := runtime.PruneVolumes(r.Context())
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, pruned)
for k, v := range pruned {
reports = append(reports, &entities.VolumePruneReport{
Err: v,
Id: k,
})
}
utils.WriteResponse(w, http.StatusOK, reports)
}
func RemoveVolume(w http.ResponseWriter, r *http.Request) {

View File

@ -133,19 +133,6 @@ type ContainerTopOKBody struct {
dockerContainer.ContainerTopOKBody
}
// swagger:model PodCreateConfig
type PodCreateConfig struct {
Name string `json:"name"`
CGroupParent string `json:"cgroup-parent"`
Hostname string `json:"hostname"`
Infra bool `json:"infra"`
InfraCommand string `json:"infra-command"`
InfraImage string `json:"infra-image"`
Labels []string `json:"labels"`
Publish []string `json:"publish"`
Share string `json:"share"`
}
type ErrorModel struct {
Message string `json:"message"`
}

View File

@ -37,7 +37,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// description: attributes for creating a pod
// schema:
// type: object
// $ref: "#/definitions/PodCreateConfig"
// $ref: "#/definitions/PodSpecGenerator"
// responses:
// 200:
// $ref: "#/definitions/IdResponse"

View File

@ -5,15 +5,33 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/specgen"
jsoniter "github.com/json-iterator/go"
)
func CreatePod() error {
// TODO
return bindings.ErrNotImplemented
func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entities.PodCreateReport, error) {
var (
pcr entities.PodCreateReport
)
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
specgenString, err := jsoniter.MarshalToString(s)
if err != nil {
return nil, err
}
stringReader := strings.NewReader(specgenString)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil)
if err != nil {
return nil, err
}
return &pcr, response.Process(&pcr)
}
// Exists is a lightweight method to determine if a pod exists in local storage

View File

@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/bindings/pods"
"github.com/containers/libpod/pkg/specgen"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
@ -307,4 +308,15 @@ var _ = Describe("Podman pods", func() {
Expect(err).To(BeNil())
Expect(len(podSummary)).To(Equal(0))
})
It("simple create pod", func() {
ps := specgen.PodSpecGenerator{}
ps.Name = "foobar"
_, err := pods.CreatePodFromSpec(bt.conn, &ps)
Expect(err).To(BeNil())
exists, err := pods.Exists(bt.conn, "foobar")
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
})
})

View File

@ -15,7 +15,7 @@ type ContainerEngine interface {
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
PodExists(ctx context.Context, nameOrId string) (*BoolReport, error)
PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error)
PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error)

View File

@ -1,6 +1,10 @@
package entities
import "time"
import (
"time"
"github.com/containers/libpod/pkg/specgen"
)
type PodKillOptions struct {
All bool
@ -92,3 +96,48 @@ type PodRmReport struct {
Err error
Id string
}
type PodCreateOptions struct {
CGroupParent string
Hostname string
Infra bool
InfraImage string
InfraCommand string
Labels map[string]string
Name string
Net *NetOptions
Share []string
}
type PodCreateReport struct {
Id string
}
func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) {
// Basic Config
s.Name = p.Name
s.Hostname = p.Hostname
s.Labels = p.Labels
s.NoInfra = !p.Infra
s.InfraCommand = []string{p.InfraCommand}
s.InfraImage = p.InfraImage
s.SharedNamespaces = p.Share
// Networking config
s.NetNS = p.Net.Network
s.StaticIP = p.Net.StaticIP
s.StaticMAC = p.Net.StaticMAC
s.PortMappings = p.Net.PublishPorts
s.CNINetworks = p.Net.CNINetworks
if p.Net.DNSHost {
s.NoManageResolvConf = true
}
s.DNSServer = p.Net.DNSServers
s.DNSSearch = p.Net.DNSSearch
s.DNSOption = p.Net.DNSOptions
s.NoManageHosts = p.Net.NoHosts
s.HostAdd = p.Net.AddHosts
// Cgroup
s.CgroupParent = p.CGroupParent
}

View File

@ -1,5 +1,12 @@
package entities
import (
"net"
"github.com/containers/libpod/pkg/specgen"
"github.com/cri-o/ocicni/pkg/ocicni"
)
type Container struct {
IdOrNamed
}
@ -15,3 +22,23 @@ type Report struct {
type PodDeleteReport struct{ Report }
type PodPruneOptions struct{}
type PodPruneReport struct{ Report }
type VolumeDeleteOptions struct{}
type VolumeDeleteReport struct{ Report }
// NetOptions reflect the shared network options between
// pods and containers
type NetOptions struct {
AddHosts []string
CNINetworks []string
DNSHost bool
DNSOptions []string
DNSSearch []string
DNSServers []net.IP
Network specgen.Namespace
NoHosts bool
PublishPorts []ocicni.PortMapping
StaticIP *net.IP
StaticMAC *net.HardwareAddr
}

View File

@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -239,3 +240,13 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
}
return reports, nil
}
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
podSpec := specgen.NewPodSpecGenerator()
opts.ToPodSpecGen(podSpec)
pod, err := podSpec.MakePod(ic.Libpod)
if err != nil {
return nil, err
}
return &entities.PodCreateReport{Id: pod.ID()}, nil
}

View File

@ -113,7 +113,20 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
}
func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
return ic.Libpod.PruneVolumes(ctx)
var (
reports []*entities.VolumePruneReport
)
pruned, err := ic.Libpod.PruneVolumes(ctx)
if err != nil {
return nil, err
}
for k, v := range pruned {
reports = append(reports, &entities.VolumePruneReport{
Err: v,
Id: k,
})
}
return reports, nil
}
func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) {

View File

@ -5,6 +5,7 @@ import (
"github.com/containers/libpod/pkg/bindings/pods"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/specgen"
)
func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
@ -170,3 +171,9 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
}
return reports, nil
}
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
podSpec := specgen.NewPodSpecGenerator()
opts.ToPodSpecGen(podSpec)
return pods.CreatePodFromSpec(ic.ClientCxt, podSpec)
}

View File

@ -13,7 +13,7 @@ import (
// MakeContainer creates a container based on the SpecGenerator
func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) {
if err := s.validate(rt); err != nil {
if err := s.validate(); err != nil {
return nil, errors.Wrap(err, "invalid config provided")
}
rtc, err := rt.GetConfig()

View File

@ -4,8 +4,6 @@ import (
"strings"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
@ -25,7 +23,7 @@ func exclusiveOptions(opt1, opt2 string) error {
// Validate verifies that the given SpecGenerator is valid and satisfies required
// input for creating a container.
func (s *SpecGenerator) validate(rt *libpod.Runtime) error {
func (s *SpecGenerator) validate() error {
//
// ContainerBasicConfig

83
pkg/specgen/pod_create.go Normal file
View File

@ -0,0 +1,83 @@
package specgen
import (
"context"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/sirupsen/logrus"
)
func (p *PodSpecGenerator) MakePod(rt *libpod.Runtime) (*libpod.Pod, error) {
if err := p.validate(); err != nil {
return nil, err
}
options, err := p.createPodOptions()
if err != nil {
return nil, err
}
return rt.NewPod(context.Background(), options...)
}
func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) {
var (
options []libpod.PodCreateOption
)
if !p.NoInfra {
options = append(options, libpod.WithInfraContainer())
nsOptions, err := shared.GetNamespaceOptions(p.SharedNamespaces)
if err != nil {
return nil, err
}
options = append(options, nsOptions...)
}
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 len(p.Hostname) > 0 {
options = append(options, libpod.WithPodHostname(p.Hostname))
}
if len(p.HostAdd) > 0 {
options = append(options, libpod.WithPodHosts(p.HostAdd))
}
if len(p.DNSOption) > 0 {
options = append(options, libpod.WithPodDNSOption(p.DNSOption))
}
if len(p.DNSSearch) > 0 {
options = append(options, libpod.WithPodDNSSearch(p.DNSSearch))
}
if p.StaticIP != nil {
options = append(options, libpod.WithPodStaticIP(*p.StaticIP))
}
if p.StaticMAC != nil {
options = append(options, libpod.WithPodStaticMAC(*p.StaticMAC))
}
if p.NoManageResolvConf {
options = append(options, libpod.WithPodUseImageResolvConf())
}
switch p.NetNS.NSMode {
case Bridge:
logrus.Debugf("Pod using default network mode")
case Host:
logrus.Debugf("Pod will use host networking")
options = append(options, libpod.WithPodHostNetwork())
default:
logrus.Debugf("Pod joining CNI networks: %v", p.CNINetworks)
options = append(options, libpod.WithPodNetworks(p.CNINetworks))
}
if p.NoManageHosts {
options = append(options, libpod.WithPodUseImageHosts())
}
if len(p.PortMappings) > 0 {
options = append(options, libpod.WithInfraContainerPorts(p.PortMappings))
}
options = append(options, libpod.WithPodCgroups())
return options, nil
}

104
pkg/specgen/pod_validate.go Normal file
View File

@ -0,0 +1,104 @@
package specgen
import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
)
var (
// ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid
ErrInvalidPodSpecConfig error = errors.New("invalid pod spec")
)
func exclusivePodOptions(opt1, opt2 string) error {
return errors.Wrapf(ErrInvalidPodSpecConfig, "%s and %s are mutually exclusive pod options", opt1, opt2)
}
func (p *PodSpecGenerator) validate() error {
// PodBasicConfig
if p.NoInfra {
if len(p.InfraCommand) > 0 {
return exclusivePodOptions("NoInfra", "InfraCommand")
}
if len(p.InfraImage) > 0 {
return exclusivePodOptions("NoInfra", "InfraImage")
}
if len(p.SharedNamespaces) > 0 {
return exclusivePodOptions("NoInfo", "SharedNamespaces")
}
}
// PodNetworkConfig
if err := p.NetNS.validate(); err != nil {
return err
}
if p.NoInfra {
if p.NetNS.NSMode == NoNetwork {
return errors.New("NoInfra and a none network cannot be used toegther")
}
if p.StaticIP != nil {
return exclusivePodOptions("NoInfra", "StaticIP")
}
if p.StaticMAC != nil {
return exclusivePodOptions("NoInfra", "StaticMAC")
}
if len(p.DNSOption) > 0 {
return exclusivePodOptions("NoInfra", "DNSOption")
}
if len(p.DNSSearch) > 0 {
return exclusivePodOptions("NoInfo", "DNSSearch")
}
if len(p.DNSServer) > 0 {
return exclusivePodOptions("NoInfra", "DNSServer")
}
if len(p.HostAdd) > 0 {
return exclusivePodOptions("NoInfra", "HostAdd")
}
if p.NoManageResolvConf {
return exclusivePodOptions("NoInfra", "NoManageResolvConf")
}
}
if p.NetNS.NSMode != Bridge {
if len(p.PortMappings) > 0 {
return errors.New("PortMappings can only be used with Bridge mode networking")
}
if len(p.CNINetworks) > 0 {
return errors.New("CNINetworks can only be used with Bridge mode networking")
}
}
if p.NoManageResolvConf {
if len(p.DNSServer) > 0 {
return exclusivePodOptions("NoManageResolvConf", "DNSServer")
}
if len(p.DNSSearch) > 0 {
return exclusivePodOptions("NoManageResolvConf", "DNSSearch")
}
if len(p.DNSOption) > 0 {
return exclusivePodOptions("NoManageResolvConf", "DNSOption")
}
}
if p.NoManageHosts && len(p.HostAdd) > 0 {
return exclusivePodOptions("NoManageHosts", "HostAdd")
}
if err := p.NetNS.validate(); err != nil {
return err
}
// Set Defaults
if p.NetNS.Value == "" {
if rootless.IsRootless() {
p.NetNS.NSMode = Slirp
} else {
p.NetNS.NSMode = Bridge
}
}
if len(p.InfraImage) < 1 {
p.InfraImage = define.DefaultInfraImage
}
if len(p.InfraCommand) < 1 {
p.InfraCommand = []string{define.DefaultInfraCommand}
}
return nil
}

View File

@ -138,3 +138,16 @@ type PodCgroupConfig struct {
// Optional.
CgroupParent string `json:"cgroup_parent,omitempty"`
}
// PodSpecGenerator describes options to create a pod
// swagger:model PodSpecGenerator
type PodSpecGenerator struct {
PodBasicConfig
PodNetworkConfig
PodCgroupConfig
}
// NewPodSpecGenerator creates a new pod spec
func NewPodSpecGenerator() *PodSpecGenerator {
return &PodSpecGenerator{}
}

View File

@ -394,18 +394,18 @@ type SpecGenerator struct {
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
func NewSpecGenerator(image string) *SpecGenerator {
net := ContainerNetworkConfig{
networkConfig := ContainerNetworkConfig{
NetNS: Namespace{
NSMode: Bridge,
},
}
csc := ContainerStorageConfig{Image: image}
if rootless.IsRootless() {
net.NetNS.NSMode = Slirp
networkConfig.NetNS.NSMode = Slirp
}
return &SpecGenerator{
ContainerStorageConfig: csc,
ContainerNetworkConfig: net,
ContainerNetworkConfig: networkConfig,
}
}

View File

@ -6,7 +6,7 @@ import (
"encoding/json"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
)
@ -113,11 +113,11 @@ func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error {
if err != nil {
return call.ReplyVolumesPrune([]string{}, []string{err.Error()})
}
for _, i := range responses {
if i.Err == nil {
prunedNames = append(prunedNames, i.Id)
for k, v := range responses {
if v == nil {
prunedNames = append(prunedNames, k)
} else {
prunedErrors = append(prunedErrors, i.Err.Error())
prunedErrors = append(prunedErrors, v.Error())
}
}
return call.ReplyVolumesPrune(prunedNames, prunedErrors)

View File

@ -12,11 +12,11 @@ t GET libpod/pods/notfoo/exists 404
t GET libpod/pods/foo/json 200 \
.Config.name=foo \
.Config.id=$pod_id \
.Containers=null
.Containers\|length=1
t GET libpod/pods/json 200 \
.[0].Name=foo \
.[0].Id=$pod_id \
.[0].Containers=null
.[0].Containers\|length=1
# Cannot create a dup pod with the same name
t POST libpod/pods/create name=foo 409 .cause="pod already exists"
@ -34,7 +34,7 @@ fi
t POST libpod/pods/foo/unpause '' 200
t POST libpod/pods/foo/unpause '' 200 # (2nd time)
t POST libpod/pods/foo/stop '' 304
t POST libpod/pods/foo/restart '' 500 .cause="no such container"
t POST libpod/pods/foo/restart '' 200
t POST libpod/pods/bar/restart '' 404