apiv2 container create using specgen

this uses the specgen structure to create containers rather than the outdated createconfig.  right now, only the apiv2 create is wired up.  eventually the cli will also have to be done.

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-02-02 09:39:12 -06:00
parent f2bcc9cc7d
commit d65ff6b3ec
24 changed files with 1391 additions and 123 deletions

View File

@ -1,4 +1,4 @@
package handlers
package generic
import (
"encoding/json"
@ -6,10 +6,10 @@ import (
"net/http"
"strings"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
image2 "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/signal"
@ -17,14 +17,13 @@ import (
"github.com/containers/storage"
"github.com/gorilla/schema"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func CreateContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
input := CreateContainerConfig{}
input := handlers.CreateContainerConfig{}
query := struct {
Name string `schema:"name"`
}{
@ -52,34 +51,11 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
return
}
cc.Name = query.Name
var pod *libpod.Pod
ctr, err := shared.CreateContainerFromCreateConfig(runtime, &cc, r.Context(), pod)
if err != nil {
if strings.Contains(err.Error(), "invalid log driver") {
// this does not quite work yet and needs a little more massaging
w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
w.WriteHeader(http.StatusInternalServerError)
msg := fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type)
if _, err := fmt.Fprintln(w, msg); err != nil {
log.Errorf("%s: %q", msg, err)
}
//s.WriteResponse(w, http.StatusInternalServerError, fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type))
return
}
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()"))
return
}
response := ContainerCreateResponse{
ID: ctr.ID(),
Warnings: []string{}}
utils.WriteResponse(w, http.StatusCreated, response)
utils.CreateContainer(r.Context(), w, runtime, &cc)
}
func makeCreateConfig(input CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
var (
err error
init bool

View File

@ -1,13 +1,15 @@
package generic
import "github.com/containers/libpod/pkg/api/handlers"
import (
"github.com/containers/libpod/pkg/api/handlers/utils"
)
// Create container
// swagger:response ContainerCreateResponse
type swagCtrCreateResponse struct {
// in:body
Body struct {
handlers.ContainerCreateResponse
utils.ContainerCreateResponse
}
}

View File

@ -0,0 +1,29 @@
package libpod
import (
"encoding/json"
"net/http"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
)
// CreateContainer takes a specgenerator and makes a container. It returns
// the new container ID on success along with any warnings.
func CreateContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
var sg specgen.SpecGenerator
if err := json.NewDecoder(r.Body).Decode(&sg); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
ctr, err := sg.MakeContainer(runtime)
if err != nil {
utils.InternalServerError(w, err)
return
}
response := utils.ContainerCreateResponse{ID: ctr.ID()}
utils.WriteJSON(w, http.StatusCreated, response)
}

View File

@ -99,12 +99,10 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http_code, err)
return
}
utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()})
utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.ID()})
}
func Pods(w http.ResponseWriter, r *http.Request) {
// 200 ok
// 500 internal
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
podInspectData []*libpod.PodInspect

View File

@ -151,6 +151,7 @@ type ContainerTopOKBody struct {
dockerContainer.ContainerTopOKBody
}
// swagger:model PodCreateConfig
type PodCreateConfig struct {
Name string `json:"name"`
CGroupParent string `json:"cgroup-parent"`
@ -548,11 +549,3 @@ func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) {
}
return ports, nil
}
// ContainerCreateResponse is the response struct for creating a container
type ContainerCreateResponse struct {
// ID of the container created
ID string `json:"id"`
// Warnings during container creation
Warnings []string `json:"Warnings"`
}

View File

@ -1,6 +1,7 @@
package utils
import (
"context"
"fmt"
"net/http"
"syscall"
@ -9,10 +10,19 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
createconfig "github.com/containers/libpod/pkg/spec"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
// ContainerCreateResponse is the response struct for creating a container
type ContainerCreateResponse struct {
// ID of the container created
ID string `json:"id"`
// Warnings during container creation
Warnings []string `json:"Warnings"`
}
func KillContainer(w http.ResponseWriter, r *http.Request) (*libpod.Container, error) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@ -119,3 +129,18 @@ func GenerateFilterFuncsFromMap(r *libpod.Runtime, filters map[string][]string)
}
return filterFuncs, nil
}
func CreateContainer(ctx context.Context, w http.ResponseWriter, runtime *libpod.Runtime, cc *createconfig.CreateConfig) {
var pod *libpod.Pod
ctr, err := shared.CreateContainerFromCreateConfig(runtime, cc, ctx, pod)
if err != nil {
Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()"))
return
}
response := ContainerCreateResponse{
ID: ctr.ID(),
Warnings: []string{}}
WriteResponse(w, http.StatusCreated, response)
}

View File

@ -33,7 +33,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/containers/create"), s.APIHandler(handlers.CreateContainer)).Methods(http.MethodPost)
r.HandleFunc(VersionedPath("/containers/create"), s.APIHandler(generic.CreateContainer)).Methods(http.MethodPost)
// swagger:operation GET /containers/json compat listContainers
// ---
// tags:
@ -550,7 +550,31 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
libpod endpoints
*/
r.HandleFunc(VersionedPath("/libpod/containers/create"), s.APIHandler(handlers.CreateContainer)).Methods(http.MethodPost)
// swagger:operation POST /containers/create libpod libpodContainerCreate
// ---
// summary: Create a container
// tags:
// - containers
// produces:
// - application/json
// parameters:
// - in: body
// name: create
// description: attributes for creating a container
// schema:
// $ref: "#/definitions/SpecGenerator"
// responses:
// 201:
// $ref: "#/responses/ContainerCreateResponse"
// 400:
// $ref: "#/responses/BadParamError"
// 404:
// $ref: "#/responses/NoSuchContainer"
// 409:
// $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/create"), s.APIHandler(libpod.CreateContainer)).Methods(http.MethodPost)
// swagger:operation GET /libpod/containers/json libpod libpodListContainers
// ---
// tags:

View File

@ -26,6 +26,25 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/json"), s.APIHandler(libpod.Pods)).Methods(http.MethodGet)
// swagger:operation POST /libpod/pods/create pods CreatePod
// ---
// summary: Create a pod
// produces:
// - application/json
// parameters:
// - in: body
// name: create
// description: attributes for creating a pod
// schema:
// type: object
// $ref: "#/definitions/PodCreateConfig"
// responses:
// 200:
// $ref: "#/definitions/IdResponse"
// 400:
// $ref: "#/responses/BadParamError"
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/create"), s.APIHandler(libpod.PodCreate)).Methods(http.MethodPost)
// swagger:operation POST /libpod/pods/prune pods PrunePods
// ---

View File

@ -0,0 +1,30 @@
package containers
import (
"context"
"net/http"
"strings"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/specgen"
jsoniter "github.com/json-iterator/go"
)
func CreateWithSpec(ctx context.Context, s specgen.SpecGenerator) (utils.ContainerCreateResponse, error) {
var ccr utils.ContainerCreateResponse
conn, err := bindings.GetConnectionFromContext(ctx)
if err != nil {
return ccr, err
}
specgenString, err := jsoniter.MarshalToString(s)
if err != nil {
return ccr, nil
}
stringReader := strings.NewReader(specgenString)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil)
if err != nil {
return ccr, err
}
return ccr, response.Process(&ccr)
}

View File

@ -6,7 +6,7 @@ import (
"github.com/pkg/errors"
)
// ContianerImageLabel is the key of the image annotation embedding a seccomp
// ContainerImageLabel is the key of the image annotation embedding a seccomp
// profile.
const ContainerImageLabel = "io.containers.seccomp.profile"

View File

@ -32,8 +32,8 @@ func Device(d *configs.Device) spec.LinuxDevice {
}
}
// devicesFromPath computes a list of devices
func devicesFromPath(g *generate.Generator, devicePath string) error {
// DevicesFromPath computes a list of devices
func DevicesFromPath(g *generate.Generator, devicePath string) error {
devs := strings.Split(devicePath, ":")
resolvedDevicePath := devs[0]
// check if it is a symbolic link
@ -216,7 +216,7 @@ func getDevices(path string) ([]*configs.Device, error) {
return out, nil
}
func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error {
func addPrivilegedDevices(g *generate.Generator) error {
hostDevices, err := getDevices("/dev")
if err != nil {
return err
@ -280,16 +280,16 @@ func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) {
var lwds []spec.LinuxWeightDevice
ret = bio
for _, i := range c.Resources.BlkioWeightDevice {
wd, err := validateweightDevice(i)
wd, err := ValidateweightDevice(i)
if err != nil {
return ret, errors.Wrapf(err, "invalid values for blkio-weight-device")
}
wdStat, err := getStatFromPath(wd.path)
wdStat, err := GetStatFromPath(wd.Path)
if err != nil {
return ret, errors.Wrapf(err, "error getting stat from path %q", wd.path)
return ret, errors.Wrapf(err, "error getting stat from path %q", wd.Path)
}
lwd := spec.LinuxWeightDevice{
Weight: &wd.weight,
Weight: &wd.Weight,
}
lwd.Major = int64(unix.Major(wdStat.Rdev))
lwd.Minor = int64(unix.Minor(wdStat.Rdev))
@ -347,7 +347,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
if err != nil {
return []spec.LinuxThrottleDevice{}, err
}
ltdStat, err := getStatFromPath(t.path)
ltdStat, err := GetStatFromPath(t.path)
if err != nil {
return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path)
}
@ -361,7 +361,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
return ltds, nil
}
func getStatFromPath(path string) (unix.Stat_t, error) {
func GetStatFromPath(path string) (unix.Stat_t, error) {
s := unix.Stat_t{}
err := unix.Stat(path, &s)
return s, err

View File

@ -15,7 +15,7 @@ func addDevice(g *generate.Generator, device string) error {
return errors.New("function not implemented")
}
func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error {
func addPrivilegedDevices(g *generate.Generator) error {
return errors.New("function not implemented")
}
@ -27,7 +27,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
return nil, errors.New("function not implemented")
}
func devicesFromPath(g *generate.Generator, devicePath string) error {
func DevicesFromPath(g *generate.Generator, devicePath string) error {
return errors.New("function not implemented")
}

View File

@ -126,6 +126,7 @@ type SecurityConfig struct {
}
// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI
// swagger:model CreateConfig
type CreateConfig struct {
Annotations map[string]string
Args []string
@ -386,6 +387,6 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
// AddPrivilegedDevices iterates through host devices and adds all
// host devices to the spec
func (c *CreateConfig) AddPrivilegedDevices(g *generate.Generator) error {
return c.addPrivilegedDevices(g)
func AddPrivilegedDevices(g *generate.Generator) error {
return addPrivilegedDevices(g)
}

View File

@ -19,12 +19,12 @@ const Pod = "pod"
// weightDevice is a structure that holds device:weight pair
type weightDevice struct {
path string
weight uint16
Path string
Weight uint16
}
func (w *weightDevice) String() string {
return fmt.Sprintf("%s:%d", w.path, w.weight)
return fmt.Sprintf("%s:%d", w.Path, w.Weight)
}
// LinuxNS is a struct that contains namespace information
@ -59,9 +59,9 @@ func NS(s string) string {
return ""
}
// validateweightDevice validates that the specified string has a valid device-weight format
// ValidateweightDevice validates that the specified string has a valid device-weight format
// for blkio-weight-device flag
func validateweightDevice(val string) (*weightDevice, error) {
func ValidateweightDevice(val string) (*weightDevice, error) {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return nil, fmt.Errorf("bad format: %s", val)
@ -78,8 +78,8 @@ func validateweightDevice(val string) (*weightDevice, error) {
}
return &weightDevice{
path: split[0],
weight: uint16(weight),
Path: split[0],
Weight: uint16(weight),
}, nil
}

View File

@ -16,9 +16,9 @@ import (
"github.com/pkg/errors"
)
const cpuPeriod = 100000
const CpuPeriod = 100000
func getAvailableGids() (int64, error) {
func GetAvailableGids() (int64, error) {
idMap, err := user.ParseIDMapFile("/proc/self/gid_map")
if err != nil {
return 0, err
@ -80,7 +80,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
gid5Available := true
if isRootless {
nGids, err := getAvailableGids()
nGids, err := GetAvailableGids()
if err != nil {
return nil, err
}
@ -197,8 +197,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
addedResources = true
}
if config.Resources.CPUs != 0 {
g.SetLinuxResourcesCPUPeriod(cpuPeriod)
g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * cpuPeriod))
g.SetLinuxResourcesCPUPeriod(CpuPeriod)
g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * CpuPeriod))
addedResources = true
}
if config.Resources.CPURtRuntime != 0 {
@ -223,12 +223,12 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
// If privileged, we need to add all the host devices to the
// spec. We do not add the user provided ones because we are
// already adding them all.
if err := config.AddPrivilegedDevices(&g); err != nil {
if err := AddPrivilegedDevices(&g); err != nil {
return nil, err
}
} else {
for _, devicePath := range config.Devices {
if err := devicesFromPath(&g, devicePath); err != nil {
if err := DevicesFromPath(&g, devicePath); err != nil {
return nil, err
}
}
@ -268,7 +268,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
}
blockAccessToKernelFilesystems(config, &g)
BlockAccessToKernelFilesystems(config.Security.Privileged, config.Pid.PidMode.IsHost(), &g)
// RESOURCES - PIDS
if config.Resources.PidsLimit > 0 {
@ -332,9 +332,9 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
// BIND MOUNTS
configSpec.Mounts = supercedeUserMounts(userMounts, configSpec.Mounts)
configSpec.Mounts = SupercedeUserMounts(userMounts, configSpec.Mounts)
// Process mounts to ensure correct options
finalMounts, err := initFSMounts(configSpec.Mounts)
finalMounts, err := InitFSMounts(configSpec.Mounts)
if err != nil {
return nil, err
}
@ -416,8 +416,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
return configSpec, nil
}
func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) {
if !config.Security.Privileged {
func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) {
if !privileged {
for _, mp := range []string{
"/proc/acpi",
"/proc/kcore",
@ -433,7 +433,7 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator)
g.AddLinuxMaskedPaths(mp)
}
if config.Pid.PidMode.IsHost() && rootless.IsRootless() {
if pidModeIsHost && rootless.IsRootless() {
return
}

View File

@ -825,7 +825,7 @@ func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, err
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
// one mount, and we already have /tmp/a and /tmp/b, should we remove
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
if len(mounts) > 0 {
// If we have overlappings mounts, remove them from the spec in favor of
// the user-added volume mounts
@ -854,7 +854,7 @@ func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M
}
// Ensure mount options on all mounts are correct
func initFSMounts(inputMounts []spec.Mount) ([]spec.Mount, error) {
func InitFSMounts(inputMounts []spec.Mount) ([]spec.Mount, error) {
// We need to look up mounts so we can figure out the proper mount flags
// to apply.
systemMounts, err := pmount.GetMounts()

View File

@ -0,0 +1,62 @@
// +build linux,cgo
package specgen
import (
"context"
"io/ioutil"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/seccomp"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
goSeccomp "github.com/seccomp/containers-golang"
"github.com/sirupsen/logrus"
)
func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
var seccompConfig *spec.LinuxSeccomp
var err error
scp, err := seccomp.LookupPolicy(s.SeccompPolicy)
if err != nil {
return nil, err
}
if scp == seccomp.PolicyImage {
labels, err := img.Labels(context.Background())
if err != nil {
return nil, err
}
imagePolicy := labels[seccomp.ContainerImageLabel]
if len(imagePolicy) < 1 {
return nil, errors.New("no seccomp policy defined by image")
}
logrus.Debug("Loading seccomp profile from the security config")
seccompConfig, err = goSeccomp.LoadProfile(imagePolicy, configSpec)
if err != nil {
return nil, errors.Wrap(err, "loading seccomp profile failed")
}
return seccompConfig, nil
}
if s.SeccompProfilePath != "" {
logrus.Debugf("Loading seccomp profile from %q", s.SeccompProfilePath)
seccompProfile, err := ioutil.ReadFile(s.SeccompProfilePath)
if err != nil {
return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", s.SeccompProfilePath)
}
seccompConfig, err = goSeccomp.LoadProfile(string(seccompProfile), configSpec)
if err != nil {
return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath)
}
} else {
logrus.Debug("Loading default seccomp profile")
seccompConfig, err = goSeccomp.GetDefaultProfile(configSpec)
if err != nil {
return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath)
}
}
return seccompConfig, nil
}

View File

@ -0,0 +1,11 @@
// +build linux,!cgo
package specgen
import (
spec "github.com/opencontainers/runtime-spec/specs-go"
)
func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
return nil, nil
}

View File

@ -0,0 +1,12 @@
// +build !linux
package specgen
import (
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
return nil, errors.New("function not supported on non-linux OS's")
}

187
pkg/specgen/create.go Normal file
View File

@ -0,0 +1,187 @@
package specgen
import (
"context"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/config"
"github.com/containers/libpod/libpod/define"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"os"
)
// MakeContainer creates a container based on the SpecGenerator
func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) {
var pod *libpod.Pod
if err := s.validate(rt); err != nil {
return nil, errors.Wrap(err, "invalid config provided")
}
rtc, err := rt.GetConfig()
if err != nil {
return nil, err
}
options, err := s.createContainerOptions(rt, pod)
if err != nil {
return nil, err
}
podmanPath, err := os.Executable()
if err != nil {
return nil, err
}
options = append(options, s.createExitCommandOption(rtc, podmanPath))
newImage, err := rt.ImageRuntime().NewFromLocal(s.Image)
if err != nil {
return nil, err
}
// TODO mheon wants to talk with Dan about this
useImageVolumes := s.ImageVolumeMode == "bind"
options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, useImageVolumes))
runtimeSpec, err := s.toOCISpec(rt, newImage)
if err != nil {
return nil, err
}
return rt.NewContainer(context.Background(), runtimeSpec, options...)
}
func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
if s.Stdin {
options = append(options, libpod.WithStdin())
}
if len(s.Systemd) > 0 {
options = append(options, libpod.WithSystemd())
}
if len(s.Name) > 0 {
logrus.Debugf("setting container name %s", s.Name)
options = append(options, libpod.WithName(s.Name))
}
if s.Pod != "" {
logrus.Debugf("adding container to pod %s", s.Pod)
options = append(options, rt.WithPod(pod))
}
destinations := []string{}
// // Take all mount and named volume destinations.
for _, mount := range s.Mounts {
destinations = append(destinations, mount.Destination)
}
for _, volume := range s.Volumes {
destinations = append(destinations, volume.Dest)
}
options = append(options, libpod.WithUserVolumes(destinations))
if len(s.Volumes) != 0 {
options = append(options, libpod.WithNamedVolumes(s.Volumes))
}
if len(s.Command) != 0 {
options = append(options, libpod.WithCommand(s.Command))
}
options = append(options, libpod.WithEntrypoint(s.Entrypoint))
if s.StopSignal != nil {
options = append(options, libpod.WithStopSignal(*s.StopSignal))
}
if s.StopTimeout != nil {
options = append(options, libpod.WithStopTimeout(*s.StopTimeout))
}
if s.LogConfiguration != nil {
if len(s.LogConfiguration.Path) > 0 {
options = append(options, libpod.WithLogPath(s.LogConfiguration.Path))
}
if len(s.LogConfiguration.Options) > 0 && s.LogConfiguration.Options["tag"] != "" {
// Note: I'm really guessing here.
options = append(options, libpod.WithLogTag(s.LogConfiguration.Options["tag"]))
}
if len(s.LogConfiguration.Driver) > 0 {
options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver))
}
}
// Security options
if len(s.SelinuxOpts) > 0 {
options = append(options, libpod.WithSecLabels(s.SelinuxOpts))
}
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
namespaceOptions, err := s.generateNamespaceContainerOpts(rt)
if err != nil {
return nil, err
}
options = append(options, namespaceOptions...)
// TODO NetworkNS still needs to be done!
if len(s.ConmonPidFile) > 0 {
options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile))
}
options = append(options, libpod.WithLabels(s.Labels))
if s.ShmSize != nil {
options = append(options, libpod.WithShmSize(*s.ShmSize))
}
if s.Rootfs != "" {
options = append(options, libpod.WithRootFS(s.Rootfs))
}
// Default used if not overridden on command line
if s.RestartPolicy != "" {
if s.RestartPolicy == "unless-stopped" {
return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported")
}
if s.RestartRetries != nil {
options = append(options, libpod.WithRestartRetries(*s.RestartRetries))
}
options = append(options, libpod.WithRestartPolicy(s.RestartPolicy))
}
if s.ContainerHealthCheckConfig.HealthConfig != nil {
options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig))
logrus.Debugf("New container has a health check")
}
return options, nil
}
func (s *SpecGenerator) createExitCommandOption(config *config.Config, podmanPath string) libpod.CtrCreateOption {
// We need a cleanup process for containers in the current model.
// But we can't assume that the caller is Podman - it could be another
// user of the API.
// As such, provide a way to specify a path to Podman, so we can
// still invoke a cleanup process.
command := []string{podmanPath,
"--root", config.StorageConfig.GraphRoot,
"--runroot", config.StorageConfig.RunRoot,
"--log-level", logrus.GetLevel().String(),
"--cgroup-manager", config.CgroupManager,
"--tmpdir", config.TmpDir,
}
if config.OCIRuntime != "" {
command = append(command, []string{"--runtime", config.OCIRuntime}...)
}
if config.StorageConfig.GraphDriverName != "" {
command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...)
}
for _, opt := range config.StorageConfig.GraphDriverOptions {
command = append(command, []string{"--storage-opt", opt}...)
}
if config.EventsLogger != "" {
command = append(command, []string{"--events-backend", config.EventsLogger}...)
}
// TODO Mheon wants to leave this for now
//if s.sys {
// command = append(command, "--syslog", "true")
//}
command = append(command, []string{"container", "cleanup"}...)
if s.Remove {
command = append(command, "--rm")
}
return libpod.WithExitCommand(command)
}

467
pkg/specgen/namespaces.go Normal file
View File

@ -0,0 +1,467 @@
package specgen
import (
"os"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/capabilities"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type NamespaceMode string
const (
// Host means the the namespace is derived from
// the host
Host NamespaceMode = "host"
// Path is the path to a namespace
Path NamespaceMode = "path"
// FromContainer means namespace is derived from a
// different container
FromContainer NamespaceMode = "container"
// FromPod indicates the namespace is derived from a pod
FromPod NamespaceMode = "pod"
// Private indicates the namespace is private
Private NamespaceMode = "private"
// NoNetwork indicates no network namespace should
// be joined. loopback should still exists
NoNetwork NamespaceMode = "none"
// Bridge indicates that a CNI network stack
// should be used
Bridge NamespaceMode = "bridge"
// Slirp indicates that a slirp4ns network stack should
// be used
Slirp NamespaceMode = "slirp4ns"
)
// Namespace describes the namespace
type Namespace struct {
NSMode NamespaceMode `json:"nsmode,omitempty"`
Value string `json:"string,omitempty"`
}
// IsHost returns a bool if the namespace is host based
func (n *Namespace) IsHost() bool {
return n.NSMode == Host
}
// IsPath indicates via bool if the namespace is based on a path
func (n *Namespace) IsPath() bool {
return n.NSMode == Path
}
// IsContainer indicates via bool if the namespace is based on a container
func (n *Namespace) IsContainer() bool {
return n.NSMode == FromContainer
}
// IsPod indicates via bool if the namespace is based on a pod
func (n *Namespace) IsPod() bool {
return n.NSMode == FromPod
}
// IsPrivate indicates the namespace is private
func (n *Namespace) IsPrivate() bool {
return n.NSMode == Private
}
// validate perform simple validation on the namespace to make sure it is not
// invalid from the get-go
func (n *Namespace) validate() error {
if n == nil {
return nil
}
switch n.NSMode {
case Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp:
break
default:
return errors.Errorf("invalid network %q", n.NSMode)
}
// Path and From Container MUST have a string value set
if n.NSMode == Path || n.NSMode == FromContainer {
if len(n.Value) < 1 {
return errors.Errorf("namespace mode %s requires a value", n.NSMode)
}
} else {
// All others must NOT set a string value
if len(n.Value) > 0 {
return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
}
}
return nil
}
func (s *SpecGenerator) generateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
var portBindings []ocicni.PortMapping
options := make([]libpod.CtrCreateOption, 0)
// Cgroups
switch {
case s.CgroupNS.IsPrivate():
ns := s.CgroupNS.Value
if _, err := os.Stat(ns); err != nil {
return nil, err
}
case s.CgroupNS.IsContainer():
connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value)
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value)
}
options = append(options, libpod.WithCgroupNSFrom(connectedCtr))
// TODO
//default:
// return nil, errors.New("cgroup name only supports private and container")
}
if s.CgroupParent != "" {
options = append(options, libpod.WithCgroupParent(s.CgroupParent))
}
if s.CgroupsMode != "" {
options = append(options, libpod.WithCgroupsMode(s.CgroupsMode))
}
// ipc
switch {
case s.IpcNS.IsHost():
options = append(options, libpod.WithShmDir("/dev/shm"))
case s.IpcNS.IsContainer():
connectedCtr, err := rt.LookupContainer(s.IpcNS.Value)
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value)
}
options = append(options, libpod.WithIPCNSFrom(connectedCtr))
options = append(options, libpod.WithShmDir(connectedCtr.ShmDir()))
}
// pid
if s.PidNS.IsContainer() {
connectedCtr, err := rt.LookupContainer(s.PidNS.Value)
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value)
}
options = append(options, libpod.WithPIDNSFrom(connectedCtr))
}
// uts
switch {
case s.UtsNS.IsPod():
connectedPod, err := rt.LookupPod(s.UtsNS.Value)
if err != nil {
return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value)
}
options = append(options, libpod.WithUTSNSFromPod(connectedPod))
case s.UtsNS.IsContainer():
connectedCtr, err := rt.LookupContainer(s.UtsNS.Value)
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value)
}
options = append(options, libpod.WithUTSNSFrom(connectedCtr))
}
if s.UseImageHosts {
options = append(options, libpod.WithUseImageHosts())
} else if len(s.HostAdd) > 0 {
options = append(options, libpod.WithHosts(s.HostAdd))
}
// User
switch {
case s.UserNS.IsPath():
ns := s.UserNS.Value
if ns == "" {
return nil, errors.Errorf("invalid empty user-defined user namespace")
}
_, err := os.Stat(ns)
if err != nil {
return nil, err
}
if s.IDMappings != nil {
options = append(options, libpod.WithIDMappings(*s.IDMappings))
}
case s.UserNS.IsContainer():
connectedCtr, err := rt.LookupContainer(s.UserNS.Value)
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value)
}
options = append(options, libpod.WithUserNSFrom(connectedCtr))
default:
if s.IDMappings != nil {
options = append(options, libpod.WithIDMappings(*s.IDMappings))
}
}
options = append(options, libpod.WithUser(s.User))
options = append(options, libpod.WithGroups(s.Groups))
if len(s.PortMappings) > 0 {
portBindings = s.PortMappings
}
switch {
case s.NetNS.IsPath():
ns := s.NetNS.Value
if ns == "" {
return nil, errors.Errorf("invalid empty user-defined network namespace")
}
_, err := os.Stat(ns)
if err != nil {
return nil, err
}
case s.NetNS.IsContainer():
connectedCtr, err := rt.LookupContainer(s.NetNS.Value)
if err != nil {
return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value)
}
options = append(options, libpod.WithNetNSFrom(connectedCtr))
case !s.NetNS.IsHost() && s.NetNS.NSMode != NoNetwork:
postConfigureNetNS := !s.UserNS.IsHost()
options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks))
}
if len(s.DNSSearch) > 0 {
options = append(options, libpod.WithDNSSearch(s.DNSSearch))
}
if len(s.DNSServer) > 0 {
// TODO I'm not sure how we are going to handle this given the input
if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" {
options = append(options, libpod.WithUseImageResolvConf())
} else {
var dnsServers []string
for _, d := range s.DNSServer {
dnsServers = append(dnsServers, d.String())
}
options = append(options, libpod.WithDNS(dnsServers))
}
}
if len(s.DNSOption) > 0 {
options = append(options, libpod.WithDNSOption(s.DNSOption))
}
if s.StaticIP != nil {
options = append(options, libpod.WithStaticIP(*s.StaticIP))
}
if s.StaticMAC != nil {
options = append(options, libpod.WithStaticMAC(*s.StaticMAC))
}
return options, nil
}
func (s *SpecGenerator) pidConfigureGenerator(g *generate.Generator) error {
if s.PidNS.IsPath() {
return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value)
}
if s.PidNS.IsHost() {
return g.RemoveLinuxNamespace(string(spec.PIDNamespace))
}
if s.PidNS.IsContainer() {
logrus.Debugf("using container %s pidmode", s.PidNS.Value)
}
if s.PidNS.IsPod() {
logrus.Debug("using pod pidmode")
}
return nil
}
func (s *SpecGenerator) utsConfigureGenerator(g *generate.Generator, runtime *libpod.Runtime) error {
hostname := s.Hostname
var err error
if hostname == "" {
switch {
case s.UtsNS.IsContainer():
utsCtr, err := runtime.GetContainer(s.UtsNS.Value)
if err != nil {
return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value)
}
hostname = utsCtr.Hostname()
case s.NetNS.IsHost() || s.UtsNS.IsHost():
hostname, err = os.Hostname()
if err != nil {
return errors.Wrap(err, "unable to retrieve hostname of the host")
}
default:
logrus.Debug("No hostname set; container's hostname will default to runtime default")
}
}
g.RemoveHostname()
if s.Hostname != "" || !s.UtsNS.IsHost() {
// Set the hostname in the OCI configuration only
// if specified by the user or if we are creating
// a new UTS namespace.
g.SetHostname(hostname)
}
g.AddProcessEnv("HOSTNAME", hostname)
if s.UtsNS.IsPath() {
return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value)
}
if s.UtsNS.IsHost() {
return g.RemoveLinuxNamespace(string(spec.UTSNamespace))
}
if s.UtsNS.IsContainer() {
logrus.Debugf("using container %s utsmode", s.UtsNS.Value)
}
return nil
}
func (s *SpecGenerator) ipcConfigureGenerator(g *generate.Generator) error {
if s.IpcNS.IsPath() {
return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value)
}
if s.IpcNS.IsHost() {
return g.RemoveLinuxNamespace(s.IpcNS.Value)
}
if s.IpcNS.IsContainer() {
logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value)
}
return nil
}
func (s *SpecGenerator) cgroupConfigureGenerator(g *generate.Generator) error {
if s.CgroupNS.IsPath() {
return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value)
}
if s.CgroupNS.IsHost() {
return g.RemoveLinuxNamespace(s.CgroupNS.Value)
}
if s.CgroupNS.IsPrivate() {
return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "")
}
if s.CgroupNS.IsContainer() {
logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value)
}
return nil
}
func (s *SpecGenerator) networkConfigureGenerator(g *generate.Generator) error {
switch {
case s.NetNS.IsHost():
logrus.Debug("Using host netmode")
if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil {
return err
}
case s.NetNS.NSMode == NoNetwork:
logrus.Debug("Using none netmode")
case s.NetNS.NSMode == Bridge:
logrus.Debug("Using bridge netmode")
case s.NetNS.IsContainer():
logrus.Debugf("using container %s netmode", s.NetNS.Value)
case s.NetNS.IsPath():
logrus.Debug("Using ns netmode")
if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil {
return err
}
case s.NetNS.IsPod():
logrus.Debug("Using pod netmode, unless pod is not sharing")
case s.NetNS.NSMode == Slirp:
logrus.Debug("Using slirp4netns netmode")
default:
return errors.Errorf("unknown network mode")
}
if g.Config.Annotations == nil {
g.Config.Annotations = make(map[string]string)
}
if s.PublishImagePorts {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
} else {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
}
return nil
}
func (s *SpecGenerator) userConfigureGenerator(g *generate.Generator) error {
if s.UserNS.IsPath() {
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil {
return err
}
// runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping
g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
}
if s.IDMappings != nil {
if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() {
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
return err
}
}
for _, uidmap := range s.IDMappings.UIDMap {
g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
}
for _, gidmap := range s.IDMappings.GIDMap {
g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
}
}
return nil
}
func (s *SpecGenerator) securityConfigureGenerator(g *generate.Generator, newImage *image.Image) error {
// HANDLE CAPABILITIES
// NOTE: Must happen before SECCOMP
if s.Privileged {
g.SetupPrivileged(true)
}
useNotRoot := func(user string) bool {
if user == "" || user == "root" || user == "0" {
return false
}
return true
}
configSpec := g.Config
var err error
var caplist []string
bounding := configSpec.Process.Capabilities.Bounding
if useNotRoot(s.User) {
configSpec.Process.Capabilities.Bounding = caplist
}
caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop)
if err != nil {
return err
}
configSpec.Process.Capabilities.Bounding = caplist
configSpec.Process.Capabilities.Permitted = caplist
configSpec.Process.Capabilities.Inheritable = caplist
configSpec.Process.Capabilities.Effective = caplist
configSpec.Process.Capabilities.Ambient = caplist
if useNotRoot(s.User) {
caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop)
if err != nil {
return err
}
}
configSpec.Process.Capabilities.Bounding = caplist
// HANDLE SECCOMP
if s.SeccompProfilePath != "unconfined" {
seccompConfig, err := s.getSeccompConfig(configSpec, newImage)
if err != nil {
return err
}
configSpec.Linux.Seccomp = seccompConfig
}
// Clear default Seccomp profile from Generator for privileged containers
if s.SeccompProfilePath == "unconfined" || s.Privileged {
configSpec.Linux.Seccomp = nil
}
g.SetRootReadonly(s.ReadOnlyFilesystem)
for sysctlKey, sysctlVal := range s.Sysctl {
g.AddLinuxSysctl(sysctlKey, sysctlVal)
}
return nil
}

260
pkg/specgen/oci.go Normal file
View File

@ -0,0 +1,260 @@
package specgen
import (
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
createconfig "github.com/containers/libpod/pkg/spec"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
)
func (s *SpecGenerator) toOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
var (
inUserNS bool
)
cgroupPerm := "ro"
g, err := generate.New("linux")
if err != nil {
return nil, err
}
// Remove the default /dev/shm mount to ensure we overwrite it
g.RemoveMount("/dev/shm")
g.HostSpecific = true
addCgroup := true
canMountSys := true
isRootless := rootless.IsRootless()
if isRootless {
inUserNS = true
}
if !s.UserNS.IsHost() {
if s.UserNS.IsContainer() || s.UserNS.IsPath() {
inUserNS = true
}
if s.UserNS.IsPrivate() {
inUserNS = true
}
}
if inUserNS && s.NetNS.IsHost() {
canMountSys = false
}
if s.Privileged && canMountSys {
cgroupPerm = "rw"
g.RemoveMount("/sys")
sysMnt := spec.Mount{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"rprivate", "nosuid", "noexec", "nodev", "rw"},
}
g.AddMount(sysMnt)
} else if !canMountSys {
addCgroup = false
g.RemoveMount("/sys")
r := "ro"
if s.Privileged {
r = "rw"
}
sysMnt := spec.Mount{
Destination: "/sys",
Type: "bind", // should we use a constant for this, like createconfig?
Source: "/sys",
Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"},
}
g.AddMount(sysMnt)
if !s.Privileged && isRootless {
g.AddLinuxMaskedPaths("/sys/kernel")
}
}
gid5Available := true
if isRootless {
nGids, err := createconfig.GetAvailableGids()
if err != nil {
return nil, err
}
gid5Available = nGids >= 5
}
// When using a different user namespace, check that the GID 5 is mapped inside
// the container.
if gid5Available && (s.IDMappings != nil && len(s.IDMappings.GIDMap) > 0) {
mappingFound := false
for _, r := range s.IDMappings.GIDMap {
if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size {
mappingFound = true
break
}
}
if !mappingFound {
gid5Available = false
}
}
if !gid5Available {
// If we have no GID mappings, the gid=5 default option would fail, so drop it.
g.RemoveMount("/dev/pts")
devPts := spec.Mount{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"},
}
g.AddMount(devPts)
}
if inUserNS && s.IpcNS.IsHost() {
g.RemoveMount("/dev/mqueue")
devMqueue := spec.Mount{
Destination: "/dev/mqueue",
Type: "bind", // constant ?
Source: "/dev/mqueue",
Options: []string{"bind", "nosuid", "noexec", "nodev"},
}
g.AddMount(devMqueue)
}
if inUserNS && s.PidNS.IsHost() {
g.RemoveMount("/proc")
procMount := spec.Mount{
Destination: "/proc",
Type: createconfig.TypeBind,
Source: "/proc",
Options: []string{"rbind", "nosuid", "noexec", "nodev"},
}
g.AddMount(procMount)
}
if addCgroup {
cgroupMnt := spec.Mount{
Destination: "/sys/fs/cgroup",
Type: "cgroup",
Source: "cgroup",
Options: []string{"rprivate", "nosuid", "noexec", "nodev", "relatime", cgroupPerm},
}
g.AddMount(cgroupMnt)
}
g.SetProcessCwd(s.WorkDir)
g.SetProcessArgs(s.Command)
g.SetProcessTerminal(s.Terminal)
for key, val := range s.Annotations {
g.AddAnnotation(key, val)
}
g.AddProcessEnv("container", "podman")
g.Config.Linux.Resources = s.ResourceLimits
// Devices
if s.Privileged {
// If privileged, we need to add all the host devices to the
// spec. We do not add the user provided ones because we are
// already adding them all.
if err := createconfig.AddPrivilegedDevices(&g); err != nil {
return nil, err
}
} else {
for _, device := range s.Devices {
if err := createconfig.DevicesFromPath(&g, device.Path); err != nil {
return nil, err
}
}
}
// SECURITY OPTS
g.SetProcessNoNewPrivileges(s.NoNewPrivileges)
if !s.Privileged {
g.SetProcessApparmorProfile(s.ApparmorProfile)
}
createconfig.BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g)
for name, val := range s.Env {
g.AddProcessEnv(name, val)
}
// TODO rlimits and ulimits needs further refinement by someone more
// familiar with the code.
//if err := addRlimits(config, &g); err != nil {
// return nil, err
//}
// NAMESPACES
if err := s.pidConfigureGenerator(&g); err != nil {
return nil, err
}
if err := s.userConfigureGenerator(&g); err != nil {
return nil, err
}
if err := s.networkConfigureGenerator(&g); err != nil {
return nil, err
}
if err := s.utsConfigureGenerator(&g, rt); err != nil {
return nil, err
}
if err := s.ipcConfigureGenerator(&g); err != nil {
return nil, err
}
if err := s.cgroupConfigureGenerator(&g); err != nil {
return nil, err
}
configSpec := g.Config
if err := s.securityConfigureGenerator(&g, newImage); err != nil {
return nil, err
}
// BIND MOUNTS
configSpec.Mounts = createconfig.SupercedeUserMounts(s.Mounts, configSpec.Mounts)
// Process mounts to ensure correct options
finalMounts, err := createconfig.InitFSMounts(configSpec.Mounts)
if err != nil {
return nil, err
}
configSpec.Mounts = finalMounts
// Add annotations
if configSpec.Annotations == nil {
configSpec.Annotations = make(map[string]string)
}
// TODO cidfile is not in specgen; when wiring up cli, we will need to move this out of here
// leaving as a reminder
//if config.CidFile != "" {
// configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile
//}
if s.Remove {
configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
} else {
configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
}
if len(s.VolumesFrom) > 0 {
configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
}
if s.Privileged {
configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
} else {
configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
}
// TODO Init might not make it into the specgen and therefore is not available here. We should deal
// with this when we wire up the CLI; leaving as a reminder
//if s.Init {
// configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue
//} else {
// configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse
//}
return configSpec, nil
}

View File

@ -2,24 +2,28 @@ package specgen
import (
"net"
"syscall"
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
// TODO
// mheon provided this an off the cuff suggestion. Adding it here to retain
// for history as we implement it. When this struct is implemented, we need
// to remove the nolints.
type Namespace struct {
isHost bool //nolint
isPath string //nolint
isContainer string //nolint
isPod bool //nolint
// LogConfig describes the logging characteristics for a container
type LogConfig struct {
// LogDriver is the container's log driver.
// Optional.
Driver string `json:"driver,omitempty"`
// LogPath is the path the container's logs will be stored at.
// Only available if LogDriver is set to "json-file" or "k8s-file".
// Optional.
Path string `json:"path,omitempty"`
// A set of options to accompany the log driver.
// Optional.
Options map[string]string `json:"options,omitempty"`
}
// ContainerBasicConfig contains the basic parts of a container.
@ -62,7 +66,7 @@ type ContainerBasicConfig struct {
// If not provided, the default, SIGTERM, will be used.
// Will conflict with Systemd if Systemd is set to "true" or "always".
// Optional.
StopSignal *uint `json:"stop_signal,omitempty"`
StopSignal *syscall.Signal `json:"stop_signal,omitempty"`
// StopTimeout is a timeout between the container's stop signal being
// sent and SIGKILL being sent.
// If not provided, the default will be used.
@ -70,13 +74,10 @@ type ContainerBasicConfig struct {
// instead.
// Optional.
StopTimeout *uint `json:"stop_timeout,omitempty"`
// LogDriver is the container's log driver.
// Optional.
LogDriver string `json:"log_driver,omitempty"`
// LogPath is the path the container's logs will be stored at.
// Only available if LogDriver is set to "json-file" or "k8s-file".
// Optional.
LogPath string `json:"log_path,omitempty"`
// LogConfiguration describes the logging for a container including
// driver, path, and options.
// Optional
LogConfiguration *LogConfig `json:"log_configuration,omitempty"`
// ConmonPidFile is a path at which a PID file for Conmon will be
// placed.
// If not given, a default location will be used.
@ -111,12 +112,10 @@ type ContainerBasicConfig struct {
// Namespace is the libpod namespace the container will be placed in.
// Optional.
Namespace string `json:"namespace,omitempty"`
// PidNS is the container's PID namespace.
// It defaults to private.
// Mandatory.
PidNS Namespace `json:"pidns,omitempty"`
// UtsNS is the container's UTS namespace.
// It defaults to private.
// Must be set to Private to set Hostname.
@ -128,6 +127,11 @@ type ContainerBasicConfig struct {
// Conflicts with UtsNS if UtsNS is not set to private.
// Optional.
Hostname string `json:"hostname,omitempty"`
// Sysctl sets kernel parameters for the container
Sysctl map[string]string `json:"sysctl,omitempty"`
// Remove indicates if the container should be removed once it has been started
// and exits
Remove bool `json:"remove"`
}
// ContainerStorageConfig contains information on the storage configuration of a
@ -175,7 +179,7 @@ type ContainerStorageConfig struct {
// Mandatory.
IpcNS Namespace `json:"ipcns,omitempty"`
// ShmSize is the size of the tmpfs to mount in at /dev/shm, in bytes.
// Conflicts with ShmSize if ShmSize is not private.
// Conflicts with ShmSize if IpcNS is not private.
// Optional.
ShmSize *int64 `json:"shm_size,omitempty"`
// WorkDir is the container's working directory.
@ -234,6 +238,9 @@ type ContainerSecurityConfig struct {
// will use.
// Optional.
ApparmorProfile string `json:"apparmor_profile,omitempty"`
// SeccompPolicy determines which seccomp profile gets applied
// the container. valid values: empty,default,image
SeccompPolicy string `json:"seccomp_policy,omitempty"`
// SeccompProfilePath is the path to a JSON file containing the
// container's Seccomp profile.
// If not specified, no Seccomp profile will be used.
@ -252,7 +259,10 @@ type ContainerSecurityConfig struct {
// IDMappings are UID and GID mappings that will be used by user
// namespaces.
// Required if UserNS is private.
IDMappings storage.IDMappingOptions `json:"idmappings,omitempty"`
IDMappings *storage.IDMappingOptions `json:"idmappings,omitempty"`
// ReadOnlyFilesystem indicates that everything will be mounted
// as read-only
ReadOnlyFilesystem bool `json:"read_only_filesystem,omittempty"`
}
// ContainerCgroupConfig contains configuration information about a container's
@ -260,16 +270,13 @@ type ContainerSecurityConfig struct {
type ContainerCgroupConfig struct {
// CgroupNS is the container's cgroup namespace.
// It defaults to private.
// Conflicts with NoCgroups if not set to host.
// Mandatory.
CgroupNS Namespace `json:"cgroupns,omitempty"`
// NoCgroups indicates that the container should not create CGroups.
// Conflicts with CgroupParent and CgroupNS if CgroupNS is not set to
// host.
NoCgroups bool `json:"no_cgroups,omitempty"`
// CgroupsMode sets a policy for how cgroups will be created in the
// container, including the ability to disable creation entirely.
CgroupsMode string `json:"cgroups_mode,omitempty"`
// CgroupParent is the container's CGroup parent.
// If not set, the default for the current cgroup driver will be used.
// Conflicts with NoCgroups.
// Optional.
CgroupParent string `json:"cgroup_parent,omitempty"`
}
@ -348,7 +355,7 @@ type ContainerNetworkConfig struct {
// ContainerResourceConfig contains information on container resource limits.
type ContainerResourceConfig struct {
// ResourceLimits are resource limits to apply to the container.
// ResourceLimits are resource limits to apply to the container.,
// Can only be set as root on cgroups v1 systems, but can be set as
// rootless as well for cgroups v2.
// Optional.
@ -365,11 +372,12 @@ type ContainerResourceConfig struct {
// ContainerHealthCheckConfig describes a container healthcheck with attributes
// like command, retries, interval, start period, and timeout.
type ContainerHealthCheckConfig struct {
HealthConfig manifest.Schema2HealthConfig `json:"healthconfig,omitempty"`
HealthConfig *manifest.Schema2HealthConfig `json:"healthconfig,omitempty"`
}
// SpecGenerator creates an OCI spec and Libpod configuration options to create
// a container based on the given configuration.
// swagger:model SpecGenerator
type SpecGenerator struct {
ContainerBasicConfig
ContainerStorageConfig
@ -381,19 +389,24 @@ type SpecGenerator struct {
}
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
func NewSpecGenerator(image, rootfs *string) (*SpecGenerator, error) {
_ = image
_ = rootfs
return &SpecGenerator{}, define.ErrNotImplemented
func NewSpecGenerator(image string) *SpecGenerator {
net := ContainerNetworkConfig{
NetNS: Namespace{
NSMode: Bridge,
},
}
csc := ContainerStorageConfig{Image: image}
if rootless.IsRootless() {
net.NetNS.NSMode = Slirp
}
return &SpecGenerator{
ContainerStorageConfig: csc,
ContainerNetworkConfig: net,
}
}
// Validate verifies that the given SpecGenerator is valid and satisfies required
// input for creating a container.
func (s *SpecGenerator) Validate() error {
return define.ErrNotImplemented
}
// MakeContainer creates a container based on the SpecGenerator
func (s *SpecGenerator) MakeContainer() (*libpod.Container, error) {
return nil, define.ErrNotImplemented
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
func NewSpecGeneratorWithRootfs(rootfs string) *SpecGenerator {
csc := ContainerStorageConfig{Rootfs: rootfs}
return &SpecGenerator{ContainerStorageConfig: csc}
}

159
pkg/specgen/validate.go Normal file
View File

@ -0,0 +1,159 @@
package specgen
import (
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
var (
// ErrInvalidSpecConfig describes an error that the given SpecGenerator is invalid
ErrInvalidSpecConfig error = errors.New("invalid configuration")
// SystemDValues describes the only values that SystemD can be
SystemDValues = []string{"true", "false", "always"}
// ImageVolumeModeValues describes the only values that ImageVolumeMode can be
ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"}
)
func exclusiveOptions(opt1, opt2 string) error {
return errors.Errorf("%s and %s are mutually exclusive options", opt1, opt2)
}
// Validate verifies that the given SpecGenerator is valid and satisfies required
// input for creating a container.
func (s *SpecGenerator) validate(rt *libpod.Runtime) error {
//
// ContainerBasicConfig
//
// Rootfs and Image cannot both populated
if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 {
return errors.Wrap(ErrInvalidSpecConfig, "both image and rootfs cannot be simultaneously")
}
// Cannot set hostname and utsns
if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() {
return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace")
}
// systemd values must be true, false, or always
if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) {
return errors.Wrapf(ErrInvalidSpecConfig, "SystemD values must be one of %s", strings.Join(SystemDValues, ","))
}
//
// ContainerStorageConfig
//
// rootfs and image cannot both be set
if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 {
return exclusiveOptions("rootfs", "image")
}
// imagevolumemode must be one of ignore, tmpfs, or anonymous if given
if len(s.ContainerStorageConfig.ImageVolumeMode) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerStorageConfig.ImageVolumeMode), ImageVolumeModeValues) {
return errors.Errorf("ImageVolumeMode values must be one of %s", strings.Join(ImageVolumeModeValues, ","))
}
// shmsize conflicts with IPC namespace
if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() {
return errors.New("cannot set shmsize when creating an IPC namespace")
}
//
// ContainerSecurityConfig
//
// groups and privileged are exclusive
if len(s.Groups) > 0 && s.Privileged {
return exclusiveOptions("Groups", "privileged")
}
// capadd and privileged are exclusive
if len(s.CapAdd) > 0 && s.Privileged {
return exclusiveOptions("CapAdd", "privileged")
}
// selinuxprocesslabel and privileged are exclusive
if len(s.SelinuxProcessLabel) > 0 && s.Privileged {
return exclusiveOptions("SelinuxProcessLabel", "privileged")
}
// selinuxmounmtlabel and privileged are exclusive
if len(s.SelinuxMountLabel) > 0 && s.Privileged {
return exclusiveOptions("SelinuxMountLabel", "privileged")
}
// selinuxopts and privileged are exclusive
if len(s.SelinuxOpts) > 0 && s.Privileged {
return exclusiveOptions("SelinuxOpts", "privileged")
}
// apparmor and privileged are exclusive
if len(s.ApparmorProfile) > 0 && s.Privileged {
return exclusiveOptions("AppArmorProfile", "privileged")
}
// userns and idmappings conflict
if s.UserNS.IsPrivate() && s.IDMappings == nil {
return errors.Wrap(ErrInvalidSpecConfig, "IDMappings are required when not creating a User namespace")
}
//
// ContainerCgroupConfig
//
//
// None for now
//
// ContainerNetworkConfig
//
if !s.NetNS.IsPrivate() && s.ConfigureNetNS {
return errors.New("can only configure network namespace when creating a network a network namespace")
}
// useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption
if s.UseImageResolvConf {
if len(s.DNSServer) > 0 {
return exclusiveOptions("UseImageResolvConf", "DNSServer")
}
if len(s.DNSSearch) > 0 {
return exclusiveOptions("UseImageResolvConf", "DNSSearch")
}
if len(s.DNSOption) > 0 {
return exclusiveOptions("UseImageResolvConf", "DNSOption")
}
}
// UseImageHosts and HostAdd are exclusive
if s.UseImageHosts && len(s.HostAdd) > 0 {
return exclusiveOptions("UseImageHosts", "HostAdd")
}
// TODO the specgen does not appear to handle this? Should it
//switch config.Cgroup.Cgroups {
//case "disabled":
// if addedResources {
// return errors.New("cannot specify resource limits when cgroups are disabled is specified")
// }
// configSpec.Linux.Resources = &spec.LinuxResources{}
//case "enabled", "no-conmon", "":
// // Do nothing
//default:
// return errors.New("unrecognized option for cgroups; supported are 'default', 'disabled', 'no-conmon'")
//}
// Namespaces
if err := s.UtsNS.validate(); err != nil {
return err
}
if err := s.IpcNS.validate(); err != nil {
return err
}
if err := s.NetNS.validate(); err != nil {
return err
}
if err := s.PidNS.validate(); err != nil {
return err
}
if err := s.CgroupNS.validate(); err != nil {
return err
}
if err := s.UserNS.validate(); err != nil {
return err
}
// The following are defaults as needed by container creation
if len(s.WorkDir) < 1 {
s.WorkDir = "/"
}
return nil
}