summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/adapter/pods.go91
-rw-r--r--pkg/adapter/pods_remote.go2
-rw-r--r--pkg/api/handlers/decoder.go13
-rw-r--r--pkg/api/handlers/generic/containers_create.go (renamed from pkg/api/handlers/containers_create.go)34
-rw-r--r--pkg/api/handlers/generic/containers_stats.go16
-rw-r--r--pkg/api/handlers/generic/images.go2
-rw-r--r--pkg/api/handlers/generic/swagger.go6
-rw-r--r--pkg/api/handlers/generic/types.go55
-rw-r--r--pkg/api/handlers/libpod/containers_create.go29
-rw-r--r--pkg/api/handlers/libpod/pods.go6
-rw-r--r--pkg/api/handlers/types.go17
-rw-r--r--pkg/api/handlers/utils/containers.go25
-rw-r--r--pkg/api/server/register_containers.go68
-rw-r--r--pkg/api/server/register_images.go5
-rw-r--r--pkg/api/server/register_pods.go19
-rw-r--r--pkg/bindings/connection.go234
-rw-r--r--pkg/bindings/containers/containers.go24
-rw-r--r--pkg/bindings/containers/create.go30
-rw-r--r--pkg/bindings/containers/healthcheck.go2
-rw-r--r--pkg/bindings/containers/mount.go6
-rw-r--r--pkg/bindings/images/images.go18
-rw-r--r--pkg/bindings/images/search.go2
-rw-r--r--pkg/bindings/network/network.go6
-rw-r--r--pkg/bindings/pods/pods.go34
-rw-r--r--pkg/bindings/test/common_test.go30
-rw-r--r--pkg/bindings/test/images_test.go4
-rw-r--r--pkg/bindings/test/pods_test.go202
-rw-r--r--pkg/bindings/volumes/volumes.go8
-rw-r--r--pkg/seccomp/seccomp.go2
-rw-r--r--pkg/spec/config_linux.go18
-rw-r--r--pkg/spec/config_unsupported.go4
-rw-r--r--pkg/spec/createconfig.go5
-rw-r--r--pkg/spec/parse.go14
-rw-r--r--pkg/spec/spec.go50
-rw-r--r--pkg/spec/storage.go9
-rw-r--r--pkg/specgen/config_linux_cgo.go62
-rw-r--r--pkg/specgen/config_linux_nocgo.go11
-rw-r--r--pkg/specgen/config_unsupported.go12
-rw-r--r--pkg/specgen/create.go187
-rw-r--r--pkg/specgen/namespaces.go467
-rw-r--r--pkg/specgen/oci.go260
-rw-r--r--pkg/specgen/specgen.go99
-rw-r--r--pkg/specgen/validate.go159
-rw-r--r--pkg/varlinkapi/pods.go16
44 files changed, 2064 insertions, 299 deletions
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index 49f086ef3..0d9fa7210 100644
--- a/pkg/adapter/pods.go
+++ b/pkg/adapter/pods.go
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "net"
"os"
"path/filepath"
"strings"
@@ -248,6 +249,17 @@ func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateVa
err error
)
+ // This needs to be first, as a lot of options depend on
+ // WithInfraContainer()
+ if cli.Infra {
+ options = append(options, libpod.WithInfraContainer())
+ nsOptions, err := shared.GetNamespaceOptions(strings.Split(cli.Share, ","))
+ if err != nil {
+ return "", err
+ }
+ options = append(options, nsOptions...)
+ }
+
if cli.Flag("cgroup-parent").Changed {
options = append(options, libpod.WithPodCgroupParent(cli.CgroupParent))
}
@@ -264,17 +276,78 @@ func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateVa
options = append(options, libpod.WithPodHostname(cli.Hostname))
}
- if cli.Infra {
- options = append(options, libpod.WithInfraContainer())
- nsOptions, err := shared.GetNamespaceOptions(strings.Split(cli.Share, ","))
+ if cli.Flag("add-host").Changed {
+ options = append(options, libpod.WithPodHosts(cli.StringSlice("add-host")))
+ }
+ if cli.Flag("dns").Changed {
+ dns := cli.StringSlice("dns")
+ foundHost := false
+ for _, entry := range dns {
+ if entry == "host" {
+ foundHost = true
+ }
+ }
+ if foundHost && len(dns) > 1 {
+ return "", errors.Errorf("cannot set dns=host and still provide other DNS servers")
+ }
+ if foundHost {
+ options = append(options, libpod.WithPodUseImageResolvConf())
+ } else {
+ options = append(options, libpod.WithPodDNS(cli.StringSlice("dns")))
+ }
+ }
+ if cli.Flag("dns-opt").Changed {
+ options = append(options, libpod.WithPodDNSOption(cli.StringSlice("dns-opt")))
+ }
+ if cli.Flag("dns-search").Changed {
+ options = append(options, libpod.WithPodDNSSearch(cli.StringSlice("dns-search")))
+ }
+ if cli.Flag("ip").Changed {
+ ip := net.ParseIP(cli.String("ip"))
+ if ip == nil {
+ return "", errors.Errorf("invalid IP address %q passed to --ip", cli.String("ip"))
+ }
+
+ options = append(options, libpod.WithPodStaticIP(ip))
+ }
+ if cli.Flag("mac-address").Changed {
+ mac, err := net.ParseMAC(cli.String("mac-address"))
if err != nil {
- return "", err
+ return "", errors.Wrapf(err, "invalid MAC address %q passed to --mac-address", cli.String("mac-address"))
+ }
+
+ options = append(options, libpod.WithPodStaticMAC(mac))
+ }
+ if cli.Flag("network").Changed {
+ netValue := cli.String("network")
+ switch strings.ToLower(netValue) {
+ case "bridge":
+ // Do nothing.
+ // TODO: Maybe this should be split between slirp and
+ // bridge? Better to wait until someone asks...
+ logrus.Debugf("Pod using default network mode")
+ case "host":
+ logrus.Debugf("Pod will use host networking")
+ options = append(options, libpod.WithPodHostNetwork())
+ case "":
+ return "", errors.Errorf("invalid value passed to --net: must provide a comma-separated list of CNI networks or host")
+ default:
+ // We'll assume this is a comma-separated list of CNI
+ // networks.
+ networks := strings.Split(netValue, ",")
+ logrus.Debugf("Pod joining CNI networks: %v", networks)
+ options = append(options, libpod.WithPodNetworks(networks))
+ }
+ }
+ if cli.Flag("no-hosts").Changed {
+ if cli.Bool("no-hosts") {
+ options = append(options, libpod.WithPodUseImageHosts())
}
- options = append(options, nsOptions...)
}
- if len(cli.Publish) > 0 {
- portBindings, err := shared.CreatePortBindings(cli.Publish)
+ publish := cli.StringSlice("publish")
+ if len(publish) > 0 {
+ portBindings, err := shared.CreatePortBindings(publish)
if err != nil {
return "", err
}
@@ -497,6 +570,10 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa
}
podOptions = append(podOptions, libpod.WithPodHostname(hostname))
+ if podYAML.Spec.HostNetwork {
+ podOptions = append(podOptions, libpod.WithPodHostNetwork())
+ }
+
nsOptions, err := shared.GetNamespaceOptions(strings.Split(shared.DefaultKernelNamespaces, ","))
if err != nil {
return nil, err
diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go
index 5ef1a9216..20f089628 100644
--- a/pkg/adapter/pods_remote.go
+++ b/pkg/adapter/pods_remote.go
@@ -185,7 +185,7 @@ func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateVa
Infra: cli.Infra,
InfraCommand: cli.InfraCommand,
InfraImage: cli.InfraCommand,
- Publish: cli.Publish,
+ Publish: cli.StringSlice("publish"),
}
return iopodman.CreatePod().Call(r.Conn, pc)
diff --git a/pkg/api/handlers/decoder.go b/pkg/api/handlers/decoder.go
index 890d77ecc..03b86275d 100644
--- a/pkg/api/handlers/decoder.go
+++ b/pkg/api/handlers/decoder.go
@@ -3,8 +3,10 @@ package handlers
import (
"encoding/json"
"reflect"
+ "syscall"
"time"
+ "github.com/containers/libpod/pkg/util"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
@@ -17,6 +19,9 @@ func NewAPIDecoder() *schema.Decoder {
d.IgnoreUnknownKeys(true)
d.RegisterConverter(map[string][]string{}, convertUrlValuesString)
d.RegisterConverter(time.Time{}, convertTimeString)
+
+ var Signal syscall.Signal
+ d.RegisterConverter(Signal, convertSignal)
return d
}
@@ -89,3 +94,11 @@ func convertTimeString(query string) reflect.Value {
func ParseDateTime(query string) time.Time {
return convertTimeString(query).Interface().(time.Time)
}
+
+func convertSignal(query string) reflect.Value {
+ signal, err := util.ParseSignal(query)
+ if err != nil {
+ logrus.Infof("convertSignal: Failed to parse %s: %s", query, err.Error())
+ }
+ return reflect.ValueOf(signal)
+}
diff --git a/pkg/api/handlers/containers_create.go b/pkg/api/handlers/generic/containers_create.go
index 48f0de94d..7e542752f 100644
--- a/pkg/api/handlers/containers_create.go
+++ b/pkg/api/handlers/generic/containers_create.go
@@ -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
diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go
index 19e2cc882..977979741 100644
--- a/pkg/api/handlers/generic/containers_stats.go
+++ b/pkg/api/handlers/generic/containers_stats.go
@@ -7,7 +7,6 @@ import (
"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/cgroups"
docker "github.com/docker/docker/api/types"
@@ -58,17 +57,18 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
}
var preRead time.Time
- var preCPUStats docker.CPUStats
+ var preCPUStats CPUStats
if query.Stream {
preRead = time.Now()
systemUsage, _ := cgroups.GetSystemCPUUsage()
- preCPUStats = docker.CPUStats{
+ preCPUStats = CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: stats.CPUNano,
PercpuUsage: stats.PerCPU,
UsageInKernelmode: stats.CPUSystemNano,
UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
},
+ CPU: stats.CPU,
SystemUsage: systemUsage,
OnlineCPUs: 0,
ThrottlingData: docker.ThrottlingData{},
@@ -124,9 +124,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
}
systemUsage, _ := cgroups.GetSystemCPUUsage()
-
- s := handlers.Stats{StatsJSON: docker.StatsJSON{
- Stats: docker.Stats{
+ s := StatsJSON{
+ Stats: Stats{
Read: time.Now(),
PreRead: preRead,
PidsStats: docker.PidsStats{
@@ -143,13 +142,14 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
IoTimeRecursive: nil,
SectorsRecursive: nil,
},
- CPUStats: docker.CPUStats{
+ CPUStats: CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: cgroupStat.CPU.Usage.Total,
PercpuUsage: cgroupStat.CPU.Usage.PerCPU,
UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
},
+ CPU: stats.CPU,
SystemUsage: systemUsage,
OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
ThrottlingData: docker.ThrottlingData{
@@ -173,7 +173,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
Name: stats.Name,
ID: stats.ContainerID,
Networks: net,
- }}
+ }
utils.WriteJSON(w, http.StatusOK, s)
if flusher, ok := w.(http.Flusher); ok {
diff --git a/pkg/api/handlers/generic/images.go b/pkg/api/handlers/generic/images.go
index afd107207..1ced499d9 100644
--- a/pkg/api/handlers/generic/images.go
+++ b/pkg/api/handlers/generic/images.go
@@ -255,7 +255,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
*/
fromImage := query.FromImage
- if len(query.Tag) < 1 {
+ if len(query.Tag) >= 1 {
fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag)
}
diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go
index bfe527c41..c9c9610bb 100644
--- a/pkg/api/handlers/generic/swagger.go
+++ b/pkg/api/handlers/generic/swagger.go
@@ -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
}
}
diff --git a/pkg/api/handlers/generic/types.go b/pkg/api/handlers/generic/types.go
new file mode 100644
index 000000000..f068ac011
--- /dev/null
+++ b/pkg/api/handlers/generic/types.go
@@ -0,0 +1,55 @@
+package generic
+
+import (
+ "time"
+
+ docker "github.com/docker/docker/api/types"
+)
+
+// CPUStats aggregates and wraps all CPU related info of container
+type CPUStats struct {
+ // CPU Usage. Linux and Windows.
+ CPUUsage docker.CPUUsage `json:"cpu_usage"`
+
+ // System Usage. Linux only.
+ SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
+
+ // Online CPUs. Linux only.
+ OnlineCPUs uint32 `json:"online_cpus,omitempty"`
+
+ // Usage of CPU in %. Linux only.
+ CPU float64 `json:"cpu"`
+
+ // Throttling Data. Linux only.
+ ThrottlingData docker.ThrottlingData `json:"throttling_data,omitempty"`
+}
+
+// Stats is Ultimate struct aggregating all types of stats of one container
+type Stats struct {
+ // Common stats
+ Read time.Time `json:"read"`
+ PreRead time.Time `json:"preread"`
+
+ // Linux specific stats, not populated on Windows.
+ PidsStats docker.PidsStats `json:"pids_stats,omitempty"`
+ BlkioStats docker.BlkioStats `json:"blkio_stats,omitempty"`
+
+ // Windows specific stats, not populated on Linux.
+ NumProcs uint32 `json:"num_procs"`
+ StorageStats docker.StorageStats `json:"storage_stats,omitempty"`
+
+ // Shared stats
+ CPUStats CPUStats `json:"cpu_stats,omitempty"`
+ PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
+ MemoryStats docker.MemoryStats `json:"memory_stats,omitempty"`
+}
+
+type StatsJSON struct {
+ Stats
+
+ Name string `json:"name,omitempty"`
+ ID string `json:"id,omitempty"`
+
+ // Networks request version >=1.21
+ Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
+}
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
new file mode 100644
index 000000000..ebca41151
--- /dev/null
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -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)
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index d043b1204..f5700579b 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -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
@@ -121,7 +119,7 @@ func Pods(w http.ResponseWriter, r *http.Request) {
return
}
- if _, found := r.URL.Query()["filters"]; found {
+ if len(query.Filters) > 0 {
utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented)
return
}
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index a50f183f7..c72b0f817 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -78,10 +78,6 @@ type Container struct {
docker.ContainerCreateConfig
}
-type ContainerStats struct {
- docker.ContainerStats
-}
-
type Version struct {
docker.Version
}
@@ -143,14 +139,11 @@ type IDResponse struct {
ID string `json:"id"`
}
-type Stats struct {
- docker.StatsJSON
-}
-
type ContainerTopOKBody struct {
dockerContainer.ContainerTopOKBody
}
+// swagger:model PodCreateConfig
type PodCreateConfig struct {
Name string `json:"name"`
CGroupParent string `json:"cgroup-parent"`
@@ -548,11 +541,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"`
-}
diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go
index c9bb9cf09..402005581 100644
--- a/pkg/api/handlers/utils/containers.go
+++ b/pkg/api/handlers/utils/containers.go
@@ -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)
+}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 06877863c..6a2ba4b1e 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -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 /libpod/containers/create libpod libpodCreateContainer
+ // ---
+ // 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:
@@ -591,21 +615,21 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// name: filters
// type: string
// description: |
- // Returns a list of containers.
- // - ancestor=(<image-name>[:<tag>], <image id>, or <image@digest>)
- // - before=(<container id> or <container name>)
- // - expose=(<port>[/<proto>]|<startport-endport>/[<proto>])
- // - exited=<int> containers with exit code of <int>
- // - health=(starting|healthy|unhealthy|none)
- // - id=<ID> a container's ID
- // - is-task=(true|false)
- // - label=key or label="key=value" of a container label
- // - name=<name> a container's name
- // - network=(<network id> or <network name>)
- // - publish=(<port>[/<proto>]|<startport-endport>/[<proto>])
- // - since=(<container id> or <container name>)
- // - status=(created|restarting|running|removing|paused|exited|dead)
- // - volume=(<volume name> or <mount point destination>)
+ // A JSON encoded value of the filters (a `map[string][]string`) to process on the containers list. Available filters:
+ // - `ancestor`=(`<image-name>[:<tag>]`, `<image id>`, or `<image@digest>`)
+ // - `before`=(`<container id>` or `<container name>`)
+ // - `expose`=(`<port>[/<proto>]` or `<startport-endport>/[<proto>]`)
+ // - `exited=<int>` containers with exit code of `<int>`
+ // - `health`=(`starting`, `healthy`, `unhealthy` or `none`)
+ // - `id=<ID>` a container's ID
+ // - `is-task`=(`true` or `false`)
+ // - `label`=(`key` or `"key=value"`) of an container label
+ // - `name=<name>` a container's name
+ // - `network`=(`<network id>` or `<network name>`)
+ // - `publish`=(`<port>[/<proto>]` or `<startport-endport>/[<proto>]`)
+ // - `since`=(`<container id>` or `<container name>`)
+ // - `status`=(`created`, `restarting`, `running`, `removing`, `paused`, `exited` or `dead`)
+ // - `volume`=(`<volume name>` or `<mount point destination>`)
// produces:
// - application/json
// responses:
@@ -638,7 +662,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/prune"), s.APIHandler(handlers.PruneContainers)).Methods(http.MethodPost)
- // swagger:operation GET /libpod/containers/showmounted libpod showMountedContainers
+ // swagger:operation GET /libpod/containers/showmounted libpod libpodShowMountedContainers
// ---
// tags:
// - containers
@@ -745,7 +769,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/kill"), s.APIHandler(libpod.KillContainer)).Methods(http.MethodPost)
- // swagger:operation POST /libpod/containers/{name}/mount libpod mountContainer
+ // swagger:operation POST /libpod/containers/{name}/mount libpod libpodMountContainer
// ---
// tags:
// - containers
@@ -1023,7 +1047,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/wait"), s.APIHandler(libpod.WaitContainer)).Methods(http.MethodPost)
- // swagger:operation GET /libpod/containers/{name}/exists libpod containerExists
+ // swagger:operation GET /libpod/containers/{name}/exists libpod libpodContainerExists
// ---
// tags:
// - containers
@@ -1072,7 +1096,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/stop"), s.APIHandler(handlers.StopContainer)).Methods(http.MethodPost)
- // swagger:operation POST /libpod/containers/{name}/attach libpod libpodAttach
+ // swagger:operation POST /libpod/containers/{name}/attach libpod libpodAttachContainer
// ---
// tags:
// - containers
@@ -1127,7 +1151,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/attach"), s.APIHandler(handlers.AttachContainer)).Methods(http.MethodPost)
- // swagger:operation POST /libpod/containers/{name}/resize libpod libpodResize
+ // swagger:operation POST /libpod/containers/{name}/resize libpod libpodResizeContainer
// ---
// tags:
// - containers
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 5b9ac7952..db04ecdc9 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -648,6 +648,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - `dangling=true`
// - `label=key` or `label="key=value"` of an image label
// - `reference`=(`<image-name>[:<tag>]`)
+ // - `id`=(`<image-id>`)
// - `since`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
// type: string
// produces:
@@ -822,7 +823,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(handlers.SearchImages)).Methods(http.MethodGet)
- // swagger:operation DELETE /libpod/images/{name} libpod libpodRemoveImage
+ // swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage
// ---
// tags:
// - images
@@ -830,7 +831,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// description: Delete an image from local store
// parameters:
// - in: path
- // name: name
+ // name: name:.*
// type: string
// required: true
// description: name or ID of image to delete
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
index 1c7486711..af2330665 100644
--- a/pkg/api/server/register_pods.go
+++ b/pkg/api/server/register_pods.go
@@ -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
// ---
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index f270060a6..75f1fc6a5 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -1,22 +1,34 @@
package bindings
import (
+ "bufio"
"context"
"fmt"
"io"
+ "io/ioutil"
"net"
"net/http"
"net/url"
+ "os"
"path/filepath"
+ "strconv"
"strings"
+ "time"
"github.com/containers/libpod/pkg/api/handlers"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh"
+ "k8s.io/client-go/util/homedir"
)
var (
- defaultConnectionPath string = filepath.Join(fmt.Sprintf("v%s", handlers.MinimalApiVersion), "libpod")
+ basePath = &url.URL{
+ Scheme: "http",
+ Host: "d",
+ Path: "/v" + handlers.MinimalApiVersion + "/libpod",
+ }
)
type APIResponse struct {
@@ -25,9 +37,28 @@ type APIResponse struct {
}
type Connection struct {
- scheme string
- address string
- client *http.Client
+ _url *url.URL
+ client *http.Client
+}
+
+type valueKey string
+
+const (
+ clientKey = valueKey("client")
+)
+
+// GetClient from context build by NewConnection()
+func GetClient(ctx context.Context) (*Connection, error) {
+ c, ok := ctx.Value(clientKey).(*Connection)
+ if !ok {
+ return nil, errors.Errorf("ClientKey not set in context")
+ }
+ return c, nil
+}
+
+// JoinURL elements with '/'
+func JoinURL(elements ...string) string {
+ return strings.Join(elements, "/")
}
// NewConnection takes a URI as a string and returns a context with the
@@ -36,46 +67,81 @@ type Connection struct {
//
// A valid URI connection should be scheme://
// For example tcp://localhost:<port>
-// or unix://run/podman/podman.sock
-func NewConnection(uri string) (context.Context, error) {
- u, err := url.Parse(uri)
- if err != nil {
- return nil, err
- }
- // TODO once ssh is implemented, remove this block and
- // add it to the conditional beneath it
- if u.Scheme == "ssh" {
- return nil, ErrNotImplemented
+// or unix:///run/podman/podman.sock
+// or ssh://<user>@<host>[:port]/run/podman/podman.sock?secure=True
+func NewConnection(ctx context.Context, uri string, identity ...string) (context.Context, error) {
+ var (
+ err error
+ secure bool
+ )
+ if v, found := os.LookupEnv("PODMAN_HOST"); found {
+ uri = v
}
- if u.Scheme != "tcp" && u.Scheme != "unix" {
- return nil, errors.Errorf("%s is not a support schema", u.Scheme)
+
+ if v, found := os.LookupEnv("PODMAN_SSHKEY"); found {
+ identity = []string{v}
}
- if u.Scheme == "tcp" && !strings.HasPrefix(uri, "tcp://") {
- return nil, errors.New("tcp URIs should begin with tcp://")
+ _url, err := url.Parse(uri)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Value of PODMAN_HOST is not a valid url: %s", uri)
}
- address := u.Path
- if u.Scheme == "tcp" {
- address = u.Host
+ // Now we setup the http client to use the connection above
+ var client *http.Client
+ switch _url.Scheme {
+ case "ssh":
+ secure, err = strconv.ParseBool(_url.Query().Get("secure"))
+ if err != nil {
+ secure = false
+ }
+ client, err = sshClient(_url, identity[0], secure)
+ case "unix":
+ if !strings.HasPrefix(uri, "unix:///") {
+ // autofix unix://path_element vs unix:///path_element
+ _url.Path = JoinURL(_url.Host, _url.Path)
+ _url.Host = ""
+ }
+ client, err = unixClient(_url)
+ case "tcp":
+ if !strings.HasPrefix(uri, "tcp://") {
+ return nil, errors.New("tcp URIs should begin with tcp://")
+ }
+ client, err = tcpClient(_url)
+ default:
+ return nil, errors.Errorf("%s is not a support schema", _url.Scheme)
+ }
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme)
}
- newConn := newConnection(u.Scheme, address)
- ctx := context.WithValue(context.Background(), "conn", &newConn)
+
+ ctx = context.WithValue(ctx, clientKey, &Connection{_url, client})
if err := pingNewConnection(ctx); err != nil {
return nil, err
}
return ctx, nil
}
+func tcpClient(_url *url.URL) (*http.Client, error) {
+ return &http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial("tcp", _url.Path)
+ },
+ DisableCompression: true,
+ },
+ }, nil
+}
+
// pingNewConnection pings to make sure the RESTFUL service is up
// and running. it should only be used where initializing a connection
func pingNewConnection(ctx context.Context) error {
- conn, err := GetConnectionFromContext(ctx)
+ client, err := GetClient(ctx)
if err != nil {
return err
}
// the ping endpoint sits at / in this case
- response, err := conn.DoRequest(nil, http.MethodGet, "../../../_ping", nil)
+ response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil)
if err != nil {
return err
}
@@ -85,26 +151,58 @@ func pingNewConnection(ctx context.Context) error {
return errors.Errorf("ping response was %q", response.StatusCode)
}
-// newConnection takes a scheme and address and creates a connection from it
-func newConnection(scheme, address string) Connection {
- client := http.Client{
- Transport: &http.Transport{
- DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
- return net.Dial(scheme, address)
+func sshClient(_url *url.URL, identity string, secure bool) (*http.Client, error) {
+ auth, err := publicKey(identity)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to parse identity %s: %v\n", _url.String(), identity)
+ }
+
+ callback := ssh.InsecureIgnoreHostKey()
+ if secure {
+ key := hostKey(_url.Hostname())
+ if key != nil {
+ callback = ssh.FixedHostKey(key)
+ }
+ }
+
+ bastion, err := ssh.Dial("tcp",
+ net.JoinHostPort(_url.Hostname(), _url.Port()),
+ &ssh.ClientConfig{
+ User: _url.User.Username(),
+ Auth: []ssh.AuthMethod{auth},
+ HostKeyCallback: callback,
+ HostKeyAlgorithms: []string{
+ ssh.KeyAlgoRSA,
+ ssh.KeyAlgoDSA,
+ ssh.KeyAlgoECDSA256,
+ ssh.KeyAlgoECDSA384,
+ ssh.KeyAlgoECDSA521,
+ ssh.KeyAlgoED25519,
},
+ Timeout: 5 * time.Second,
},
+ )
+ if err != nil {
+ return nil, errors.Wrapf(err, "Connection to bastion host (%s) failed.", _url.String())
}
- newConn := Connection{
- client: &client,
- address: address,
- scheme: scheme,
- }
- return newConn
+ return &http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return bastion.Dial("unix", _url.Path)
+ },
+ }}, nil
}
-func (c *Connection) makeEndpoint(u string) string {
- // The d character in the url is discarded and is meaningless
- return fmt.Sprintf("http://d/%s%s", defaultConnectionPath, u)
+func unixClient(_url *url.URL) (*http.Client, error) {
+ return &http.Client{
+ Transport: &http.Transport{
+ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
+ d := net.Dialer{}
+ return d.DialContext(ctx, "unix", _url.Path)
+ },
+ DisableCompression: true,
+ },
+ }, nil
}
// DoRequest assembles the http request and returns the response
@@ -121,7 +219,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
// Lets eventually use URL for this which might lead to safer
// usage
safeEndpoint := fmt.Sprintf(endpoint, safePathValues...)
- e := c.makeEndpoint(safeEndpoint)
+ e := basePath.String() + safeEndpoint
req, err := http.NewRequest(httpMethod, e, httpBody)
if err != nil {
return nil, err
@@ -140,21 +238,11 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
if err == nil {
break
}
+ time.Sleep(time.Duration(i*100) * time.Millisecond)
}
return &APIResponse{response, req}, err
}
-// GetConnectionFromContext returns a bindings connection from the context
-// being passed into each method.
-func GetConnectionFromContext(ctx context.Context) (*Connection, error) {
- c := ctx.Value("conn")
- if c == nil {
- return nil, errors.New("unable to get connection from context")
- }
- conn := c.(*Connection)
- return conn, nil
-}
-
// FiltersToString converts our typical filter format of a
// map[string][]string to a query/html safe string.
func FiltersToString(filters map[string][]string) (string, error) {
@@ -189,3 +277,45 @@ func (h *APIResponse) IsClientError() bool {
func (h *APIResponse) IsServerError() bool {
return h.Response.StatusCode/100 == 5
}
+
+func publicKey(path string) (ssh.AuthMethod, error) {
+ key, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ signer, err := ssh.ParsePrivateKey(key)
+ if err != nil {
+ return nil, err
+ }
+
+ return ssh.PublicKeys(signer), nil
+}
+
+func hostKey(host string) ssh.PublicKey {
+ // parse OpenSSH known_hosts file
+ // ssh or use ssh-keyscan to get initial key
+ known_hosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
+ fd, err := os.Open(known_hosts)
+ if err != nil {
+ logrus.Error(err)
+ return nil
+ }
+
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
+ if err != nil {
+ logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text())
+ continue
+ }
+
+ for _, h := range hosts {
+ if h == host {
+ return key
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index 04f7f8802..a437e9a9b 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -16,7 +16,7 @@ import (
// size information should also be included. Finally, the sync bool synchronizes the OCI runtime and
// container state.
func List(ctx context.Context, filters map[string][]string, all *bool, last *int, pod, size, sync *bool) ([]lpapiv2.ListContainer, error) { // nolint:typecheck
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -59,7 +59,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
var (
pruneResponse []string
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -82,7 +82,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
// that the container should be removed forcibly (example, even it is running). The volumes
// bool dictates that a container's volumes should also be removed.
func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -105,7 +105,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error {
// should be calculated. Calculating the size of a container requires extra work from the filesystem and
// is therefore slower.
func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectContainerData, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -125,7 +125,7 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectC
// representation of a signal like 'SIGKILL'. The nameOrID can be a container name
// or a partial/full ID
func Kill(ctx context.Context, nameOrID string, signal string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -143,7 +143,7 @@ func Logs() {}
// Pause pauses a given container. The nameOrID can be a container name
// or a partial/full ID.
func Pause(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -158,7 +158,7 @@ func Pause(ctx context.Context, nameOrID string) error {
// or a partial/full ID. The optional timeout specifies the number of seconds to wait
// for the running container to stop before killing it.
func Restart(ctx context.Context, nameOrID string, timeout *int) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -177,7 +177,7 @@ func Restart(ctx context.Context, nameOrID string, timeout *int) error {
// or a partial/full ID. The optional parameter for detach keys are to override the default
// detach key sequence.
func Start(ctx context.Context, nameOrID string, detachKeys *string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -198,7 +198,7 @@ func Top() {}
// Unpause resumes the given paused container. The nameOrID can be a container name
// or a partial/full ID.
func Unpause(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -213,7 +213,7 @@ func Unpause(ctx context.Context, nameOrID string) error {
// or a partial/full ID.
func Wait(ctx context.Context, nameOrID string) (int32, error) {
var exitCode int32
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return exitCode, err
}
@@ -228,7 +228,7 @@ func Wait(ctx context.Context, nameOrID string) (int32, error) {
// exists in local storage. The nameOrID can be a container name
// or a partial/full ID.
func Exists(ctx context.Context, nameOrID string) (bool, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return false, err
}
@@ -243,7 +243,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
// or a partial/full ID
func Stop(ctx context.Context, nameOrID string, timeout *int) error {
params := make(map[string]string)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go
new file mode 100644
index 000000000..2943cb522
--- /dev/null
+++ b/pkg/bindings/containers/create.go
@@ -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.GetClient(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)
+}
diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go
index 9ed7f858d..dc607c1b3 100644
--- a/pkg/bindings/containers/healthcheck.go
+++ b/pkg/bindings/containers/healthcheck.go
@@ -11,7 +11,7 @@ import (
// RunHealthCheck executes the container's healthcheck and returns the health status of the
// container.
func RunHealthCheck(ctx context.Context, nameOrID string) (*libpod.HealthCheckStatus, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go
index d68dee981..e0627d9a3 100644
--- a/pkg/bindings/containers/mount.go
+++ b/pkg/bindings/containers/mount.go
@@ -10,7 +10,7 @@ import (
// Mount mounts an existing container to the filesystem. It returns the path
// of the mounted container in string format.
func Mount(ctx context.Context, nameOrID string) (string, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return "", err
}
@@ -27,7 +27,7 @@ func Mount(ctx context.Context, nameOrID string) (string, error) {
// Unmount unmounts a container from the filesystem. The container must not be running
// or the unmount will fail.
func Unmount(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -40,7 +40,7 @@ func Unmount(ctx context.Context, nameOrID string) error {
// GetMountedContainerPaths returns a map of mounted containers and their mount locations.
func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index b19482943..271d58952 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -14,7 +14,7 @@ import (
// Exists a lightweight way to determine if an image exists in local storage. It returns a
// boolean response.
func Exists(ctx context.Context, nameOrID string) (bool, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return false, err
}
@@ -29,7 +29,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
// ways to alter the image query.
func List(ctx context.Context, all *bool, filters map[string][]string) ([]*handlers.ImageSummary, error) {
var imageSummary []*handlers.ImageSummary
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -54,7 +54,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*handl
// Get performs an image inspect. To have the on-disk size of the image calculated, you can
// use the optional size parameter.
func GetImage(ctx context.Context, nameOrID string, size *bool) (*inspect.ImageData, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -77,7 +77,7 @@ func ImageTree(ctx context.Context, nameOrId string) error {
// History returns the parent layers of an image.
func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, error) {
var history []*handlers.HistoryResponse
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -89,7 +89,7 @@ func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse,
}
func Load(ctx context.Context, r io.Reader) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -103,7 +103,7 @@ func Load(ctx context.Context, r io.Reader) error {
// the image by removing all all containers, including those that are Running, first.
func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) {
var deletes []map[string]string
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -121,7 +121,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]str
// Export saves an image from local storage as a tarball or image archive. The optional format
// parameter is used to change the format of the output.
func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, compress *bool) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -149,7 +149,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
var (
deleted []string
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -170,7 +170,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
// Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required.
func Tag(ctx context.Context, nameOrID, tag, repo string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
diff --git a/pkg/bindings/images/search.go b/pkg/bindings/images/search.go
index 58b25425b..dca1b0e63 100644
--- a/pkg/bindings/images/search.go
+++ b/pkg/bindings/images/search.go
@@ -16,7 +16,7 @@ func Search(ctx context.Context, term string, limit *int, filters map[string][]s
var (
searchResults []image.SearchResult
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
index 97bbb8c42..c95b22953 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -10,7 +10,7 @@ import (
func Create() {}
func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -23,7 +23,7 @@ func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, erro
}
func Remove(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -38,7 +38,7 @@ func List(ctx context.Context) ([]*libcni.NetworkConfigList, error) {
var (
netList []*libcni.NetworkConfigList
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
index d079f01c2..838b22e43 100644
--- a/pkg/bindings/pods/pods.go
+++ b/pkg/bindings/pods/pods.go
@@ -16,7 +16,7 @@ func CreatePod() error {
// Exists is a lightweight method to determine if a pod exists in local storage
func Exists(ctx context.Context, nameOrID string) (bool, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return false, err
}
@@ -29,7 +29,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
// Inspect returns low-level information about the given pod.
func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -44,7 +44,7 @@ func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) {
// Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter
// can be used to override SIGTERM.
func Kill(ctx context.Context, nameOrID string, signal *string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -61,7 +61,7 @@ func Kill(ctx context.Context, nameOrID string, signal *string) error {
// Pause pauses all running containers in a given pod.
func Pause(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -74,7 +74,7 @@ func Pause(ctx context.Context, nameOrID string) error {
// Prune removes all non-running pods in local storage.
func Prune(ctx context.Context) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -87,11 +87,11 @@ func Prune(ctx context.Context) error {
// List returns all pods in local storage. The optional filters parameter can
// be used to refine which pods should be listed.
-func List(ctx context.Context, filters map[string][]string) (*[]libpod.PodInspect, error) {
+func List(ctx context.Context, filters map[string][]string) ([]*libpod.PodInspect, error) {
var (
- inspect []libpod.PodInspect
+ inspect []*libpod.PodInspect
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -103,16 +103,16 @@ func List(ctx context.Context, filters map[string][]string) (*[]libpod.PodInspec
}
params["filters"] = stringFilter
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/json", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params)
if err != nil {
- return &inspect, err
+ return inspect, err
}
- return &inspect, response.Process(&inspect)
+ return inspect, response.Process(&inspect)
}
// Restart restarts all containers in a pod.
func Restart(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -126,7 +126,7 @@ func Restart(ctx context.Context, nameOrID string) error {
// Remove deletes a Pod from from local storage. The optional force parameter denotes
// that the Pod can be removed even if in a running state.
func Remove(ctx context.Context, nameOrID string, force *bool) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -143,11 +143,11 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error {
// Start starts all containers in a pod.
func Start(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s/start", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID)
if err != nil {
return err
}
@@ -162,7 +162,7 @@ func Stats() error {
// Stop stops all containers in a Pod. The optional timeout parameter can be
// used to override the timeout before the container is killed.
func Stop(ctx context.Context, nameOrID string, timeout *int) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
@@ -184,7 +184,7 @@ func Top() error {
// Unpause unpauses all paused containers in a Pod.
func Unpause(ctx context.Context, nameOrID string) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index dba94cb35..98d64bbaa 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -114,7 +114,7 @@ func newBindingTest() *bindingTest {
runRoot: filepath.Join(tmpPath, "run"),
artifactDirPath: "",
imageCacheDir: "",
- sock: fmt.Sprintf("unix:%s", filepath.Join(tmpPath, "api.sock")),
+ sock: fmt.Sprintf("unix://%s", filepath.Join(tmpPath, "api.sock")),
tempDirPath: tmpPath,
}
return &b
@@ -162,16 +162,30 @@ func (b *bindingTest) restoreImageFromCache(i testImage) {
p.Wait(45)
}
-// Run a container and add append the alpine image to it
-func (b *bindingTest) RunTopContainer(name *string) {
+// Run a container within or without a pod
+// and add or append the alpine image to it
+func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, podName *string) {
cmd := []string{"run", "-dt"}
- if name != nil {
- containerName := *name
- cmd = append(cmd, "--name", containerName)
+ if *insidePod && podName != nil {
+ pName := *podName
+ cmd = append(cmd, "--pod", pName)
+ } else if containerName != nil {
+ cName := *containerName
+ cmd = append(cmd, "--name", cName)
}
cmd = append(cmd, alpine.name, "top")
- p := b.runPodman(cmd)
- p.Wait(45)
+ b.runPodman(cmd).Wait(45)
+}
+
+// This method creates a pod with the given pod name.
+// Podname is an optional parameter
+func (b *bindingTest) Podcreate(name *string) {
+ if name != nil {
+ podname := *name
+ b.runPodman([]string{"pod", "create", "--name", podname}).Wait(45)
+ } else {
+ b.runPodman([]string{"pod", "create"}).Wait(45)
+ }
}
// StringInSlice returns a boolean based on whether a given
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
index 74e0cc67a..0b51c8c9e 100644
--- a/pkg/bindings/test/images_test.go
+++ b/pkg/bindings/test/images_test.go
@@ -38,7 +38,7 @@ var _ = Describe("Podman images", func() {
bt.RestoreImagesFromCache()
s = bt.startAPIService()
time.Sleep(1 * time.Second)
- connText, err = bindings.NewConnection(bt.sock)
+ connText, err = bindings.NewConnection(context.Background(), bt.sock)
Expect(err).To(BeNil())
})
@@ -93,7 +93,7 @@ var _ = Describe("Podman images", func() {
// Start a container with alpine image
var top string = "top"
- bt.RunTopContainer(&top)
+ bt.RunTopContainer(&top, &falseFlag, nil)
// we should now have a container called "top" running
containerResponse, err := containers.Inspect(connText, "top", &falseFlag)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go
new file mode 100644
index 000000000..76ccd10f2
--- /dev/null
+++ b/pkg/bindings/test/pods_test.go
@@ -0,0 +1,202 @@
+package test_bindings
+
+import (
+ "context"
+ "net/http"
+ "time"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/pods"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman images", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ connText context.Context
+ newpod string
+ err error
+ trueFlag bool = true
+ )
+
+ BeforeEach(func() {
+ bt = newBindingTest()
+ newpod = "newpod"
+ bt.RestoreImagesFromCache()
+ bt.Podcreate(&newpod)
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ connText, err = bindings.NewConnection(bt.sock)
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("inspect pod", func() {
+ //Inspect an invalid pod name
+ _, err := pods.Inspect(connText, "dummyname")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ //Inspect an valid pod name
+ response, err := pods.Inspect(connText, newpod)
+ Expect(err).To(BeNil())
+ Expect(response.Config.Name).To(Equal(newpod))
+ })
+
+ // Test validates the list all api returns
+ It("list pod", func() {
+ //List all the pods in the current instance
+ podSummary, err := pods.List(connText, nil)
+ Expect(err).To(BeNil())
+ Expect(len(podSummary)).To(Equal(1))
+ // Adding an alpine container to the existing pod
+ bt.RunTopContainer(nil, &trueFlag, &newpod)
+ podSummary, err = pods.List(connText, nil)
+ // Verify no errors.
+ Expect(err).To(BeNil())
+ // Verify number of containers in the pod.
+ Expect(len(podSummary[0].Containers)).To(Equal(2))
+
+ // Add multiple pods and verify them by name and size.
+ var newpod2 string = "newpod2"
+ bt.Podcreate(&newpod2)
+ podSummary, err = pods.List(connText, nil)
+ Expect(len(podSummary)).To(Equal(2))
+ var names []string
+ for _, i := range podSummary {
+ names = append(names, i.Config.Name)
+ }
+ Expect(StringInSlice(newpod, names)).To(BeTrue())
+ Expect(StringInSlice("newpod2", names)).To(BeTrue())
+
+ // Not working Because: code to list based on filter
+ // "not yet implemented",
+ // Validate list pod with filters
+ filters := make(map[string][]string)
+ filters["name"] = []string{newpod}
+ filteredPods, err := pods.List(connText, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ })
+
+ // The test validates if the exists responds
+ It("exists pod", func() {
+ response, err := pods.Exists(connText, "dummyName")
+ Expect(err).To(BeNil())
+ Expect(response).To(BeFalse())
+
+ // Should exit with no error and response should be true
+ response, err = pods.Exists(connText, "newpod")
+ Expect(err).To(BeNil())
+ Expect(response).To(BeTrue())
+ })
+
+ // This test validates if All running containers within
+ // each specified pod are paused and unpaused
+ It("pause upause pod", func() {
+ // Pause invalid container
+ err := pods.Pause(connText, "dummyName")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Adding an alpine container to the existing pod
+ bt.RunTopContainer(nil, &trueFlag, &newpod)
+ response, err := pods.Inspect(connText, newpod)
+ Expect(err).To(BeNil())
+
+ // Binding needs to be modified to inspect the pod state.
+ // Since we dont have a pod state we inspect the states of the containers within the pod.
+ // Pause a valid container
+ err = pods.Pause(connText, newpod)
+ Expect(err).To(BeNil())
+ response, err = pods.Inspect(connText, newpod)
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStatePaused))
+ }
+
+ // Unpause a valid container
+ err = pods.Unpause(connText, newpod)
+ Expect(err).To(BeNil())
+ response, err = pods.Inspect(connText, newpod)
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateRunning))
+ }
+ })
+
+ It("start stop restart pod", func() {
+ // Start an invalid pod
+ err = pods.Start(connText, "dummyName")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Stop an invalid pod
+ err = pods.Stop(connText, "dummyName", nil)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Restart an invalid pod
+ err = pods.Restart(connText, "dummyName")
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Start a valid pod and inspect status of each container
+ err = pods.Start(connText, newpod)
+ Expect(err).To(BeNil())
+
+ response, err := pods.Inspect(connText, newpod)
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateRunning))
+ }
+
+ // Start a already running container
+ // (Test fails for now needs to be fixed)
+ err = pods.Start(connText, newpod)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotModified))
+
+ // Stop the running pods
+ err = pods.Stop(connText, newpod, nil)
+ Expect(err).To(BeNil())
+ response, _ = pods.Inspect(connText, newpod)
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateStopped))
+ }
+
+ // Stop a already running pod
+ // (Test fails for now needs to be fixed)
+ err = pods.Stop(connText, newpod, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotModified))
+
+ err = pods.Restart(connText, newpod)
+ Expect(err).To(BeNil())
+ response, _ = pods.Inspect(connText, newpod)
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateRunning))
+ }
+ })
+
+ // Remove all stopped pods and their container to be implemented.
+ It("prune pod", func() {
+ })
+})
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
index 05a4f73fd..8313a7460 100644
--- a/pkg/bindings/volumes/volumes.go
+++ b/pkg/bindings/volumes/volumes.go
@@ -16,7 +16,7 @@ func Create(ctx context.Context, config handlers.VolumeCreateConfig) (string, er
var (
volumeID string
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return "", err
}
@@ -32,7 +32,7 @@ func Inspect(ctx context.Context, nameOrID string) (*libpod.InspectVolumeData, e
var (
inspect libpod.InspectVolumeData
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -55,7 +55,7 @@ func Prune(ctx context.Context) ([]string, error) {
var (
pruned []string
)
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
@@ -69,7 +69,7 @@ func Prune(ctx context.Context) ([]string, error) {
// Remove deletes the given volume from storage. The optional force parameter
// is used to remove a volume even if it is being used by a container.
func Remove(ctx context.Context, nameOrID string, force *bool) error {
- conn, err := bindings.GetConnectionFromContext(ctx)
+ conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
diff --git a/pkg/seccomp/seccomp.go b/pkg/seccomp/seccomp.go
index dcf255378..eeba46a72 100644
--- a/pkg/seccomp/seccomp.go
+++ b/pkg/seccomp/seccomp.go
@@ -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"
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index 5f39b6d0d..544c0020d 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -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
diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go
index be3e7046d..568afde55 100644
--- a/pkg/spec/config_unsupported.go
+++ b/pkg/spec/config_unsupported.go
@@ -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")
}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 8010be0d4..5011df496 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -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)
}
diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go
index a5dfccdb9..38d93b87f 100644
--- a/pkg/spec/parse.go
+++ b/pkg/spec/parse.go
@@ -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
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index b2a152a2d..a4ae22efd 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -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
}
}
@@ -241,23 +241,35 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
// SECURITY OPTS
+ var runtimeConfig *libpodconfig.Config
+
+ if runtime != nil {
+ runtimeConfig, err = runtime.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ }
+
g.SetProcessNoNewPrivileges(config.Security.NoNewPrivs)
if !config.Security.Privileged {
g.SetProcessApparmorProfile(config.Security.ApparmorProfile)
}
- blockAccessToKernelFilesystems(config, &g)
-
- var runtimeConfig *libpodconfig.Config
-
- if runtime != nil {
- runtimeConfig, err = runtime.GetConfig()
- if err != nil {
+ // Unless already set via the CLI, check if we need to disable process
+ // labels or set the defaults.
+ if len(config.Security.LabelOpts) == 0 && runtimeConfig != nil {
+ if !runtimeConfig.EnableLabeling {
+ // Disabled in the config.
+ config.Security.LabelOpts = append(config.Security.LabelOpts, "disable")
+ } else if err := config.Security.SetLabelOpts(runtime, &config.Pid, &config.Ipc); err != nil {
+ // Defaults!
return nil, err
}
}
+ BlockAccessToKernelFilesystems(config.Security.Privileged, config.Pid.PidMode.IsHost(), &g)
+
// RESOURCES - PIDS
if config.Resources.PidsLimit > 0 {
// if running on rootless on a cgroupv1 machine or using the cgroupfs manager, pids
@@ -320,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
}
@@ -404,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",
@@ -421,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
}
diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go
index 0e2098c1d..c365701de 100644
--- a/pkg/spec/storage.go
+++ b/pkg/spec/storage.go
@@ -739,6 +739,7 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string
for vol := range config.BuiltinImgVolumes {
cleanDest := filepath.Clean(vol)
+ logrus.Debugf("Adding image volume at %s", cleanDest)
if config.ImageVolumeType == "tmpfs" {
// Tmpfs image volumes are handled as mounts
mount := spec.Mount{
@@ -747,13 +748,13 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string
Type: TypeTmpfs,
Options: []string{"rprivate", "rw", "nodev", "exec"},
}
- mounts[vol] = mount
+ mounts[cleanDest] = mount
} else {
// Anonymous volumes have no name.
namedVolume := new(libpod.ContainerNamedVolume)
namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"}
namedVolume.Dest = cleanDest
- volumes[vol] = namedVolume
+ volumes[cleanDest] = namedVolume
}
}
@@ -825,7 +826,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 +855,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()
diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/config_linux_cgo.go
new file mode 100644
index 000000000..6f547a40d
--- /dev/null
+++ b/pkg/specgen/config_linux_cgo.go
@@ -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
+}
diff --git a/pkg/specgen/config_linux_nocgo.go b/pkg/specgen/config_linux_nocgo.go
new file mode 100644
index 000000000..fc0c58c37
--- /dev/null
+++ b/pkg/specgen/config_linux_nocgo.go
@@ -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
+}
diff --git a/pkg/specgen/config_unsupported.go b/pkg/specgen/config_unsupported.go
new file mode 100644
index 000000000..5d24ac39c
--- /dev/null
+++ b/pkg/specgen/config_unsupported.go
@@ -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")
+}
diff --git a/pkg/specgen/create.go b/pkg/specgen/create.go
new file mode 100644
index 000000000..c8fee5f05
--- /dev/null
+++ b/pkg/specgen/create.go
@@ -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)
+}
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
new file mode 100644
index 000000000..025cb31e0
--- /dev/null
+++ b/pkg/specgen/namespaces.go
@@ -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
+}
diff --git a/pkg/specgen/oci.go b/pkg/specgen/oci.go
new file mode 100644
index 000000000..2523f21b3
--- /dev/null
+++ b/pkg/specgen/oci.go
@@ -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
+}
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index e22ee598f..e1dfe4dc5 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -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
-}
-
-// Validate verifies that the given SpecGenerator is valid and satisfies required
-// input for creating a container.
-func (s *SpecGenerator) Validate() error {
- return 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,
+ }
}
-// 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}
}
diff --git a/pkg/specgen/validate.go b/pkg/specgen/validate.go
new file mode 100644
index 000000000..78e4d8ad5
--- /dev/null
+++ b/pkg/specgen/validate.go
@@ -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
+}
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 1ebe5d424..2ec45f7a1 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -16,6 +16,14 @@ import (
// CreatePod ...
func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCreate) error {
var options []libpod.PodCreateOption
+ if create.Infra {
+ options = append(options, libpod.WithInfraContainer())
+ nsOptions, err := shared.GetNamespaceOptions(create.Share)
+ if err != nil {
+ return err
+ }
+ options = append(options, nsOptions...)
+ }
if create.CgroupParent != "" {
options = append(options, libpod.WithPodCgroupParent(create.CgroupParent))
}
@@ -43,14 +51,6 @@ func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCrea
options = append(options, libpod.WithInfraContainerPorts(portBindings))
}
- if create.Infra {
- options = append(options, libpod.WithInfraContainer())
- nsOptions, err := shared.GetNamespaceOptions(create.Share)
- if err != nil {
- return err
- }
- options = append(options, nsOptions...)
- }
options = append(options, libpod.WithPodCgroups())
pod, err := i.Runtime.NewPod(getContext(), options...)