summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/adapter/autoupdate.go11
-rw-r--r--pkg/adapter/autoupdate_remote.go11
-rw-r--r--pkg/adapter/pods.go26
-rw-r--r--pkg/adapter/pods_remote.go7
-rw-r--r--pkg/adapter/terminal_linux.go14
-rw-r--r--pkg/adapter/terminal_unsupported.go23
-rw-r--r--pkg/api/handlers/compat/containers.go4
-rw-r--r--pkg/api/handlers/compat/images_history.go2
-rw-r--r--pkg/api/handlers/libpod/containers.go6
-rw-r--r--pkg/api/handlers/libpod/manifests.go166
-rw-r--r--pkg/api/handlers/libpod/pods.go7
-rw-r--r--pkg/api/handlers/libpod/swagger.go36
-rw-r--r--pkg/api/handlers/libpod/volumes.go20
-rw-r--r--pkg/api/handlers/types.go14
-rw-r--r--pkg/api/handlers/utils/containers.go2
-rw-r--r--pkg/api/handlers/utils/pods.go45
-rw-r--r--pkg/api/server/register_events.go2
-rw-r--r--pkg/api/server/register_manifest.go145
-rw-r--r--pkg/api/server/register_swagger.go15
-rw-r--r--pkg/api/server/server.go94
-rw-r--r--pkg/api/server/swagger.go12
-rw-r--r--pkg/api/tags.yaml2
-rw-r--r--pkg/autoupdate/autoupdate.go280
-rw-r--r--pkg/autoupdate/autoupdate_test.go50
-rw-r--r--pkg/bindings/bindings.go9
-rw-r--r--pkg/bindings/connection.go9
-rw-r--r--pkg/bindings/containers/containers.go6
-rw-r--r--pkg/bindings/containers/logs.go116
-rw-r--r--pkg/bindings/containers/types.go13
-rw-r--r--pkg/bindings/manifests/manifests.go126
-rw-r--r--pkg/bindings/system/system.go61
-rw-r--r--pkg/bindings/test/common_test.go7
-rw-r--r--pkg/bindings/test/containers_test.go75
-rw-r--r--pkg/bindings/test/images_test.go26
-rw-r--r--pkg/bindings/test/manifests_test.go124
-rw-r--r--pkg/bindings/test/pods_test.go80
-rw-r--r--pkg/bindings/test/system_test.go51
-rw-r--r--pkg/bindings/test/volumes_test.go33
-rw-r--r--pkg/bindings/volumes/volumes.go6
-rw-r--r--pkg/domain/entities/containers.go23
-rw-r--r--pkg/domain/entities/engine.go96
-rw-r--r--pkg/domain/entities/engine_container.go18
-rw-r--r--pkg/domain/entities/engine_image.go12
-rw-r--r--pkg/domain/entities/filters.go150
-rw-r--r--pkg/domain/entities/images.go151
-rw-r--r--pkg/domain/entities/types.go25
-rw-r--r--pkg/domain/entities/volumes.go41
-rw-r--r--pkg/domain/infra/abi/containers.go66
-rw-r--r--pkg/domain/infra/abi/images.go131
-rw-r--r--pkg/domain/infra/abi/images_test.go37
-rw-r--r--pkg/domain/infra/abi/parse/parse.go68
-rw-r--r--pkg/domain/infra/abi/pods.go19
-rw-r--r--pkg/domain/infra/abi/runtime.go17
-rw-r--r--pkg/domain/infra/abi/volumes.go38
-rw-r--r--pkg/domain/infra/runtime_abi.go38
-rw-r--r--pkg/domain/infra/runtime_image_proxy.go21
-rw-r--r--pkg/domain/infra/runtime_libpod.go331
-rw-r--r--pkg/domain/infra/runtime_proxy.go21
-rw-r--r--pkg/domain/infra/runtime_tunnel.go35
-rw-r--r--pkg/domain/infra/tunnel/containers.go42
-rw-r--r--pkg/domain/infra/tunnel/helpers.go41
-rw-r--r--pkg/domain/infra/tunnel/images.go81
-rw-r--r--pkg/domain/infra/tunnel/pods.go13
-rw-r--r--pkg/domain/infra/tunnel/runtime.go33
-rw-r--r--pkg/domain/infra/tunnel/volumes.go16
-rw-r--r--pkg/domain/utils/utils.go41
-rw-r--r--pkg/rootless/rootless_linux.c17
-rw-r--r--pkg/rootlessport/rootlessport_linux.go23
-rw-r--r--pkg/spec/createconfig.go3
-rw-r--r--pkg/specgen/config_unsupported.go3
-rw-r--r--pkg/specgen/create.go2
-rw-r--r--pkg/specgen/pod.go140
-rw-r--r--pkg/specgen/specgen.go6
-rw-r--r--pkg/systemd/dbus.go47
-rw-r--r--pkg/systemd/generate/systemdgen.go9
-rw-r--r--pkg/systemd/generate/systemdgen_test.go7
-rw-r--r--pkg/util/utils_linux_test.go29
-rw-r--r--pkg/util/utils_test.go19
-rw-r--r--pkg/varlinkapi/containers.go16
-rw-r--r--pkg/varlinkapi/system.go2
80 files changed, 3450 insertions, 214 deletions
diff --git a/pkg/adapter/autoupdate.go b/pkg/adapter/autoupdate.go
new file mode 100644
index 000000000..01f7a29c5
--- /dev/null
+++ b/pkg/adapter/autoupdate.go
@@ -0,0 +1,11 @@
+// +build !remoteclient
+
+package adapter
+
+import (
+ "github.com/containers/libpod/pkg/autoupdate"
+)
+
+func (r *LocalRuntime) AutoUpdate() ([]string, []error) {
+ return autoupdate.AutoUpdate(r.Runtime)
+}
diff --git a/pkg/adapter/autoupdate_remote.go b/pkg/adapter/autoupdate_remote.go
new file mode 100644
index 000000000..a2a82d0d4
--- /dev/null
+++ b/pkg/adapter/autoupdate_remote.go
@@ -0,0 +1,11 @@
+// +build remoteclient
+
+package adapter
+
+import (
+ "github.com/containers/libpod/libpod/define"
+)
+
+func (r *LocalRuntime) AutoUpdate() ([]string, []error) {
+ return nil, []error{define.ErrNotImplemented}
+}
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index dc856cc8d..1417bd2b9 100644
--- a/pkg/adapter/pods.go
+++ b/pkg/adapter/pods.go
@@ -122,19 +122,31 @@ func (r *LocalRuntime) GetLatestPod() (*Pod, error) {
return &pod, err
}
+// GetPodsWithFilters gets the filtered list of pods based on the filter parameters provided.
+func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) {
+ pods, err := shared.GetPodsWithFilters(r.Runtime, filters)
+ if err != nil {
+ return nil, err
+ }
+ return r.podstoAdapterPods(pods)
+}
+
+func (r *LocalRuntime) podstoAdapterPods(pod []*libpod.Pod) ([]*Pod, error) {
+ var pods []*Pod
+ for _, i := range pod {
+
+ pods = append(pods, &Pod{i})
+ }
+ return pods, nil
+}
+
// GetAllPods gets all pods and wraps it in an adapter pod
func (r *LocalRuntime) GetAllPods() ([]*Pod, error) {
- var pods []*Pod
allPods, err := r.Runtime.GetAllPods()
if err != nil {
return nil, err
}
- for _, p := range allPods {
- pod := Pod{}
- pod.Pod = p
- pods = append(pods, &pod)
- }
- return pods, nil
+ return r.podstoAdapterPods(allPods)
}
// LookupPod gets a pod by name or id and wraps it in an adapter pod
diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go
index 20f089628..6b8f22f15 100644
--- a/pkg/adapter/pods_remote.go
+++ b/pkg/adapter/pods_remote.go
@@ -10,7 +10,7 @@ import (
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
- "github.com/containers/libpod/cmd/podman/varlink"
+ iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/varlinkapi"
@@ -208,6 +208,11 @@ func (r *LocalRuntime) GetAllPods() ([]*Pod, error) {
return pods, nil
}
+// This is a empty implementation stating remoteclient not yet implemented
+func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) {
+ return nil, define.ErrNotImplemented
+}
+
// GetPodsByStatus returns a slice of pods filtered by a libpod status
func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*Pod, error) {
podIDs, err := iopodman.GetPodsByStatus().Call(r.Conn, statuses)
diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go
index 3dc5864e2..ef5a6f926 100644
--- a/pkg/adapter/terminal_linux.go
+++ b/pkg/adapter/terminal_linux.go
@@ -16,7 +16,6 @@ import (
// ExecAttachCtr execs and attaches to a container
func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) {
resize := make(chan remotecommand.TerminalSize)
-
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
// Check if we are attached to a terminal. If we are, generate resize
@@ -33,7 +32,18 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged b
}
}()
}
- return ctr.Exec(tty, privileged, env, cmd, user, workDir, streams, preserveFDs, resize, detachKeys)
+
+ execConfig := new(libpod.ExecConfig)
+ execConfig.Command = cmd
+ execConfig.Terminal = tty
+ execConfig.Privileged = privileged
+ execConfig.Environment = env
+ execConfig.User = user
+ execConfig.WorkDir = workDir
+ execConfig.DetachKeys = &detachKeys
+ execConfig.PreserveFDs = preserveFDs
+
+ return ctr.Exec(execConfig, streams, resize)
}
// StartAttachCtr starts and (if required) attaches to a container
diff --git a/pkg/adapter/terminal_unsupported.go b/pkg/adapter/terminal_unsupported.go
new file mode 100644
index 000000000..3009f0a38
--- /dev/null
+++ b/pkg/adapter/terminal_unsupported.go
@@ -0,0 +1,23 @@
+// +build !linux
+
+package adapter
+
+import (
+ "context"
+ "os"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+)
+
+// ExecAttachCtr execs and attaches to a container
+func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) {
+ return -1, define.ErrNotImplemented
+}
+
+// StartAttachCtr starts and (if required) attaches to a container
+// if you change the signature of this function from os.File to io.Writer, it will trigger a downstream
+// error. we may need to just lint disable this one.
+func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer
+ return define.ErrNotImplemented
+}
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 1298e7fa4..3a269fe50 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -87,7 +87,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- if _, found := r.URL.Query()["limit"]; found {
+ if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 {
last := query.Limit
if len(containers) > last {
containers = containers[len(containers)-last:]
@@ -326,7 +326,6 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
builder.WriteRune(' ')
}
builder.WriteString(line.Msg)
-
// Build header and output entry
binary.BigEndian.PutUint32(header[4:], uint32(len(header)+builder.Len()))
if _, err := w.Write(header[:]); err != nil {
@@ -335,7 +334,6 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
if _, err := fmt.Fprint(w, builder.String()); err != nil {
log.Errorf("unable to write builder string: %q", err)
}
-
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go
index 04304caa4..afadf4c48 100644
--- a/pkg/api/handlers/compat/images_history.go
+++ b/pkg/api/handlers/compat/images_history.go
@@ -28,7 +28,7 @@ func HistoryImage(w http.ResponseWriter, r *http.Request) {
for _, h := range history {
l := handlers.HistoryResponse{
ID: h.ID,
- Created: h.Created.UnixNano(),
+ Created: h.Created.Unix(),
CreatedBy: h.CreatedBy,
Tags: h.Tags,
Size: h.Size,
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index 8020c391d..cdc34004f 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -21,8 +21,12 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) {
name := utils.GetName(r)
_, err := runtime.LookupContainer(name)
if err != nil {
- utils.ContainerNotFound(w, name, err)
+ if errors.Cause(err) == define.ErrNoSuchCtr {
+ utils.ContainerNotFound(w, name, err)
+ }
+ utils.InternalServerError(w, err)
return
+
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
new file mode 100644
index 000000000..a3d2caba6
--- /dev/null
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -0,0 +1,166 @@
+package libpod
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/containers/buildah/manifests"
+ copy2 "github.com/containers/image/v5/copy"
+ "github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/opencontainers/go-digest"
+ "github.com/pkg/errors"
+)
+
+func ManifestCreate(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Name []string `schema:"name"`
+ Image []string `schema:"image"`
+ All bool `schema:"all"`
+ }{
+ // Add defaults here once needed.
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+ manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manID})
+}
+
+func ManifestInspect(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ data, err := newImage.InspectManifest()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, data)
+}
+
+func ManifestAdd(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ var manifestInput image.ManifestAddOpts
+ if err := json.NewDecoder(r.Body).Decode(&manifestInput); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+ newID, err := newImage.AddManifest(*sc, manifestInput)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
+}
+
+func ManifestRemove(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Digest string `schema:"digest"`
+ }{
+ // Add defaults here once needed.
+ }
+ name := utils.GetName(r)
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ d, err := digest.Parse(query.Digest)
+ if err != nil {
+ utils.Error(w, "invalid digest", http.StatusBadRequest, err)
+ return
+ }
+ newID, err := newImage.RemoveManifest(d)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
+}
+func ManifestPush(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ All bool `schema:"all"`
+ Destination string `schema:"destination"`
+ }{
+ // Add defaults here once needed.
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ name := utils.GetName(r)
+ newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.ImageNotFound(w, name, err)
+ return
+ }
+ dest, err := alltransports.ParseImageName(query.Destination)
+ if err != nil {
+ utils.Error(w, "invalid destination parameter", http.StatusBadRequest, errors.Errorf("invalid destination parameter %q", query.Destination))
+ return
+ }
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+ opts := manifests.PushOptions{
+ ImageListSelection: copy2.CopySpecificImages,
+ SystemContext: sc,
+ }
+ if query.All {
+ opts.ImageListSelection = copy2.CopyAllImages
+ }
+ newD, err := newImage.PushManifest(dest, opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, newD.String())
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index f93c8f8d5..27ec64d89 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -103,7 +103,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
func Pods(w http.ResponseWriter, r *http.Request) {
var (
- runtime = r.Context().Value("runtime").(*libpod.Runtime)
podInspectData []*libpod.PodInspect
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@@ -118,12 +117,8 @@ func Pods(w http.ResponseWriter, r *http.Request) {
return
}
- if len(query.Filters) > 0 {
- utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented)
- return
- }
+ pods, err := utils.GetPods(w, r)
- pods, err := runtime.GetAllPods()
if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index aec30ef56..149fa10dc 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -1,8 +1,44 @@
package libpod
+import (
+ "net/http"
+ "os"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/pkg/errors"
+)
+
+// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file
+const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml"
+
// List Containers
// swagger:response ListContainers
type swagInspectPodResponse struct {
// in:body
Body []ListContainer
}
+
+// Inspect Manifest
+// swagger:response InspectManifest
+type swagInspectManifestResponse struct {
+ // in:body
+ Body manifest.List
+}
+
+func ServeSwagger(w http.ResponseWriter, r *http.Request) {
+ path := DefaultPodmanSwaggerSpec
+ if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
+ path = p
+ }
+ if _, err := os.Stat(path); err != nil {
+ if os.IsNotExist(err) {
+ utils.InternalServerError(w, errors.Errorf("file %q does not exist", path))
+ return
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+ w.Header().Set("Content-Type", "text/yaml")
+ http.ServeFile(w, r, path)
+}
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
index 9b10ee890..06ca1d225 100644
--- a/pkg/api/handlers/libpod/volumes.go
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -8,8 +8,8 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
"github.com/gorilla/schema"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@@ -25,7 +25,7 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
}{
// override any golang type defaults
}
- input := handlers.VolumeCreateConfig{}
+ input := entities.VolumeCreateOptions{}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
@@ -46,8 +46,8 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
if len(input.Label) > 0 {
volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Label))
}
- if len(input.Opts) > 0 {
- parsedOptions, err := shared.ParseVolumeOptions(input.Opts)
+ if len(input.Options) > 0 {
+ parsedOptions, err := shared.ParseVolumeOptions(input.Options)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -64,7 +64,17 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusOK, config)
+ volResponse := entities.VolumeConfigResponse{
+ Name: config.Name,
+ Labels: config.Labels,
+ Driver: config.Driver,
+ MountPoint: config.MountPoint,
+ CreatedTime: config.CreatedTime,
+ Options: config.Options,
+ UID: config.UID,
+ GID: config.GID,
+ }
+ utils.WriteResponse(w, http.StatusOK, volResponse)
}
func InspectVolume(w http.ResponseWriter, r *http.Request) {
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 2e429dc58..c6b70251b 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -128,19 +128,9 @@ type CreateContainerConfig struct {
NetworkingConfig dockerNetwork.NetworkingConfig
}
-// swagger:model VolumeCreate
-type VolumeCreateConfig struct {
- // New volume's name. Can be left blank
- Name string `schema:"name"`
- // Volume driver to use
- Driver string `schema:"driver"`
- // User-defined key/value metadata.
- Label map[string]string `schema:"label"`
- // Mapping of driver options and values.
- Opts map[string]string `schema:"opts"`
-}
-
+// swagger:model IDResponse
type IDResponse struct {
+ // ID
ID string `json:"id"`
}
diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go
index d5a79bdc8..bbe4cee3c 100644
--- a/pkg/api/handlers/utils/containers.go
+++ b/pkg/api/handlers/utils/containers.go
@@ -16,7 +16,7 @@ import (
// ContainerCreateResponse is the response struct for creating a container
type ContainerCreateResponse struct {
// ID of the container created
- ID string `json:"id"`
+ ID string `json:"Id"`
// Warnings during container creation
Warnings []string `json:"Warnings"`
}
diff --git a/pkg/api/handlers/utils/pods.go b/pkg/api/handlers/utils/pods.go
new file mode 100644
index 000000000..266ad9a4b
--- /dev/null
+++ b/pkg/api/handlers/utils/pods.go
@@ -0,0 +1,45 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/gorilla/schema"
+)
+
+func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ query := struct {
+ All bool
+ Filters map[string][]string `schema:"filters"`
+ Digests bool
+ }{}
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ return nil, err
+ }
+ var filters = []string{}
+ if _, found := r.URL.Query()["digests"]; found && query.Digests {
+ UnSupportedParameter("digests")
+ }
+
+ if len(query.Filters) > 0 {
+ for k, v := range query.Filters {
+ for _, val := range v {
+ filters = append(filters, fmt.Sprintf("%s=%s", k, val))
+ }
+ }
+ filterFuncs, err := shared.GenerateFilterFunction(runtime, filters)
+ if err != nil {
+ return nil, err
+ }
+ return shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...)
+ }
+
+ return runtime.GetAllPods()
+
+}
diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go
index b0f403709..e909303da 100644
--- a/pkg/api/server/register_events.go
+++ b/pkg/api/server/register_events.go
@@ -63,6 +63,6 @@ func (s *APIServer) registerEventsHandlers(r *mux.Router) error {
// description: returns a string of json data describing an event
// 500:
// "$ref": "#/responses/InternalError"
- r.Handle(VersionedPath("/events"), s.APIHandler(compat.GetEvents)).Methods(http.MethodGet)
+ r.Handle(VersionedPath("/libpod/events"), s.APIHandler(compat.GetEvents)).Methods(http.MethodGet)
return nil
}
diff --git a/pkg/api/server/register_manifest.go b/pkg/api/server/register_manifest.go
new file mode 100644
index 000000000..8fd84f205
--- /dev/null
+++ b/pkg/api/server/register_manifest.go
@@ -0,0 +1,145 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
+ // swagger:operation POST /libpod/manifests/create manifests Create
+ // ---
+ // summary: Create
+ // description: Create a manifest list
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: name
+ // type: string
+ // description: manifest list name
+ // required: true
+ // - in: query
+ // name: image
+ // type: string
+ // description: name of the image
+ // - in: query
+ // name: all
+ // type: boolean
+ // description: add all contents if given list
+ // responses:
+ // 200:
+ // $ref: "#/definitions/IDResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: "#/responses/NoSuchImage"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/manifests/create"), s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/manifests/{name:.*}/json manifests Inspect
+ // ---
+ // summary: Inspect
+ // description: Display a manifest list
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name:.*
+ // type: string
+ // required: true
+ // description: the name or ID of the manifest
+ // responses:
+ // 200:
+ // $ref: "#/responses/InspectManifest"
+ // 404:
+ // $ref: "#/responses/NoSuchManifest"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/manifests/{name:.*}/json"), s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/manifests/{name:.*}/add manifests AddManifest
+ // ---
+ // description: Add an image to a manifest list
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name:.*
+ // type: string
+ // required: true
+ // description: the name or ID of the manifest
+ // - in: body
+ // name: options
+ // description: options for creating a manifest
+ // schema:
+ // $ref: "#/definitions/ManifestAddOpts"
+ // responses:
+ // 200:
+ // $ref: "#/definitions/IDResponse"
+ // 404:
+ // $ref: "#/responses/NoSuchManifest"
+ // 409:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/manifests/{name:.*}/add"), s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost)
+ // swagger:operation DELETE /libpod/manifests/{name:.*} manifests RemoveManifest
+ // ---
+ // summary: Remove
+ // description: Remove an image from a manifest list
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name:.*
+ // type: string
+ // required: true
+ // description: the image associated with the manifest
+ // - in: query
+ // name: digest
+ // type: string
+ // description: image digest to be removed
+ // responses:
+ // 200:
+ // $ref: "#/definitions/IDResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: "#/responses/NoSuchManifest"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/manifests/{name:.*}"), s.APIHandler(libpod.ManifestRemove)).Methods(http.MethodDelete)
+ // swagger:operation POST /libpod/manifests/{name}/push manifests PushManifest
+ // ---
+ // summary: Push
+ // description: Push a manifest list or image index to a registry
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name or ID of the manifest
+ // - in: query
+ // name: destination
+ // type: string
+ // required: true
+ // description: the destination for the manifest
+ // - in: query
+ // name: all
+ // description: push all images
+ // type: boolean
+ // responses:
+ // 200:
+ // $ref: "#/definitions/IDResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: "#/responses/NoSuchManifest"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/manifests/{name}/push"), s.APIHandler(libpod.ManifestPush)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_swagger.go b/pkg/api/server/register_swagger.go
index 5564ec096..9048c1951 100644
--- a/pkg/api/server/register_swagger.go
+++ b/pkg/api/server/register_swagger.go
@@ -2,25 +2,14 @@ package server
import (
"net/http"
- "os"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/gorilla/mux"
)
-// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file
-const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml"
-
// RegisterSwaggerHandlers maps the swagger endpoint for the server
func (s *APIServer) RegisterSwaggerHandlers(r *mux.Router) error {
// This handler does _*NOT*_ provide an UI rather just a swagger spec that an UI could render
- r.PathPrefix("/swagger/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- path := DefaultPodmanSwaggerSpec
- if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
- path = p
- }
- w.Header().Set("Content-Type", "text/yaml")
-
- http.ServeFile(w, r, path)
- })
+ r.HandleFunc(VersionedPath("/libpod/swagger"), s.APIHandler(libpod.ServeSwagger)).Methods(http.MethodGet)
return nil
}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index a0addb303..59f1f95cb 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -83,10 +83,6 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
ConnectionCh: make(chan int),
}
- server.Timer = time.AfterFunc(server.Duration, func() {
- server.ConnectionCh <- NOOPHandler
- })
-
router.NotFoundHandler = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
// We can track user errors...
@@ -99,15 +95,17 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
server.registerAuthHandlers,
server.registerContainersHandlers,
server.registerDistributionHandlers,
- server.registerExecHandlers,
server.registerEventsHandlers,
+ server.registerExecHandlers,
server.registerHealthCheckHandlers,
server.registerImagesHandlers,
server.registerInfoHandlers,
+ server.registerManifestHandlers,
server.registerMonitorHandlers,
server.registerPingHandlers,
server.registerPluginsHandlers,
server.registerPodsHandlers,
+ server.RegisterSwaggerHandlers,
server.registerSwarmHandlers,
server.registerSystemHandlers,
server.registerVersionHandlers,
@@ -138,36 +136,15 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
// Serve starts responding to HTTP requests
func (s *APIServer) Serve() error {
- // stalker to count the connections. Should the timer expire it will shutdown the service.
- go func() {
- for delta := range s.ConnectionCh {
- switch delta {
- case EnterHandler:
- s.Timer.Stop()
- s.ActiveConnections += 1
- s.TotalConnections += 1
- case ExitHandler:
- s.Timer.Stop()
- s.ActiveConnections -= 1
- if s.ActiveConnections == 0 {
- // Server will be shutdown iff the timer expires before being reset or stopped
- s.Timer = time.AfterFunc(s.Duration, func() {
- if err := s.Shutdown(); err != nil {
- logrus.Errorf("Failed to shutdown APIServer: %v", err)
- os.Exit(1)
- }
- })
- } else {
- s.Timer.Reset(s.Duration)
- }
- case NOOPHandler:
- // push the check out another duration...
- s.Timer.Reset(s.Duration)
- default:
- logrus.Errorf("ConnectionCh received unsupported input %d", delta)
- }
- }
- }()
+ // This is initialized here as Timer is not needed until Serve'ing
+ if s.Duration > 0 {
+ s.Timer = time.AfterFunc(s.Duration, func() {
+ s.ConnectionCh <- NOOPHandler
+ })
+ go s.ReadChannelWithTimeout()
+ } else {
+ go s.ReadChannelNoTimeout()
+ }
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
@@ -192,6 +169,53 @@ func (s *APIServer) Serve() error {
return nil
}
+func (s *APIServer) ReadChannelWithTimeout() {
+ // stalker to count the connections. Should the timer expire it will shutdown the service.
+ for delta := range s.ConnectionCh {
+ switch delta {
+ case EnterHandler:
+ s.Timer.Stop()
+ s.ActiveConnections += 1
+ s.TotalConnections += 1
+ case ExitHandler:
+ s.Timer.Stop()
+ s.ActiveConnections -= 1
+ if s.ActiveConnections == 0 {
+ // Server will be shutdown iff the timer expires before being reset or stopped
+ s.Timer = time.AfterFunc(s.Duration, func() {
+ if err := s.Shutdown(); err != nil {
+ logrus.Errorf("Failed to shutdown APIServer: %v", err)
+ os.Exit(1)
+ }
+ })
+ } else {
+ s.Timer.Reset(s.Duration)
+ }
+ case NOOPHandler:
+ // push the check out another duration...
+ s.Timer.Reset(s.Duration)
+ default:
+ logrus.Warnf("ConnectionCh received unsupported input %d", delta)
+ }
+ }
+}
+
+func (s *APIServer) ReadChannelNoTimeout() {
+ // stalker to count the connections.
+ for delta := range s.ConnectionCh {
+ switch delta {
+ case EnterHandler:
+ s.ActiveConnections += 1
+ s.TotalConnections += 1
+ case ExitHandler:
+ s.ActiveConnections -= 1
+ case NOOPHandler:
+ default:
+ logrus.Warnf("ConnectionCh received unsupported input %d", delta)
+ }
+ }
+}
+
// Shutdown is a clean shutdown waiting on existing clients
func (s *APIServer) Shutdown() error {
// Duration == 0 flags no auto-shutdown of the server
diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go
index e3c991d6d..2e1a269f2 100644
--- a/pkg/api/server/swagger.go
+++ b/pkg/api/server/swagger.go
@@ -4,6 +4,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
)
// No such image
@@ -51,6 +52,15 @@ type swagErrNoSuchPod struct {
}
}
+// No such manifest
+// swagger:response NoSuchManifest
+type swagErrNoSuchManifest struct {
+ // in:body
+ Body struct {
+ utils.ErrorModel
+ }
+}
+
// Internal server error
// swagger:response InternalError
type swagInternalError struct {
@@ -146,7 +156,7 @@ type ok struct {
type swagVolumeCreateResponse struct {
// in:body
Body struct {
- libpod.VolumeConfig
+ entities.VolumeConfigResponse
}
}
diff --git a/pkg/api/tags.yaml b/pkg/api/tags.yaml
index 571f49e44..5b5d9f5bb 100644
--- a/pkg/api/tags.yaml
+++ b/pkg/api/tags.yaml
@@ -6,6 +6,8 @@ tags:
- name: images
description: Actions related to images
- name: pods
+ description: Actions related to manifests
+ - name: manifests
description: Actions related to pods
- name: volumes
description: Actions related to volumes
diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go
new file mode 100644
index 000000000..7c243eb00
--- /dev/null
+++ b/pkg/autoupdate/autoupdate.go
@@ -0,0 +1,280 @@
+package autoupdate
+
+import (
+ "context"
+ "os"
+ "sort"
+
+ "github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/docker/reference"
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/systemd"
+ systemdGen "github.com/containers/libpod/pkg/systemd/generate"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// Label denotes the container/pod label key to specify auto-update policies in
+// container labels.
+const Label = "io.containers.autoupdate"
+
+// Policy represents an auto-update policy.
+type Policy string
+
+const (
+ // PolicyDefault is the default policy denoting no auto updates.
+ PolicyDefault Policy = "disabled"
+ // PolicyNewImage is the policy to update as soon as there's a new image found.
+ PolicyNewImage = "image"
+)
+
+// Map for easy lookups of supported policies.
+var supportedPolicies = map[string]Policy{
+ "": PolicyDefault,
+ "disabled": PolicyDefault,
+ "image": PolicyNewImage,
+}
+
+// LookupPolicy looksup the corresponding Policy for the specified
+// string. If none is found, an errors is returned including the list of
+// supported policies.
+//
+// Note that an empty string resolved to PolicyDefault.
+func LookupPolicy(s string) (Policy, error) {
+ policy, exists := supportedPolicies[s]
+ if exists {
+ return policy, nil
+ }
+
+ // Sort the keys first as maps are non-deterministic.
+ keys := []string{}
+ for k := range supportedPolicies {
+ if k != "" {
+ keys = append(keys, k)
+ }
+ }
+ sort.Strings(keys)
+
+ return "", errors.Errorf("invalid auto-update policy %q: valid policies are %+q", s, keys)
+}
+
+// ValidateImageReference checks if the specified imageName is a fully-qualified
+// image reference to the docker transport (without digest). Such a reference
+// includes a domain, name and tag (e.g., quay.io/podman/stable:latest). The
+// reference may also be prefixed with "docker://" explicitly indicating that
+// it's a reference to the docker transport.
+func ValidateImageReference(imageName string) error {
+ // Make sure the input image is a docker.
+ imageRef, err := alltransports.ParseImageName(imageName)
+ if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
+ return errors.Errorf("auto updates require the docker image transport but image is of transport %q", imageRef.Transport().Name())
+ } else if err != nil {
+ repo, err := reference.Parse(imageName)
+ if err != nil {
+ return errors.Wrap(err, "error enforcing fully-qualified docker transport reference for auto updates")
+ }
+ if _, ok := repo.(reference.NamedTagged); !ok {
+ return errors.Errorf("auto updates require fully-qualified image references (no tag): %q", imageName)
+ }
+ if _, ok := repo.(reference.Digested); ok {
+ return errors.Errorf("auto updates require fully-qualified image references without digest: %q", imageName)
+ }
+ }
+ return nil
+}
+
+// AutoUpdate looks up containers with a specified auto-update policy and acts
+// accordingly. If the policy is set to PolicyNewImage, it checks if the image
+// on the remote registry is different than the local one. If the image digests
+// differ, it pulls the remote image and restarts the systemd unit running the
+// container.
+//
+// It returns a slice of successfully restarted systemd units and a slice of
+// errors encountered during auto update.
+func AutoUpdate(runtime *libpod.Runtime) ([]string, []error) {
+ // Create a map from `image ID -> []*Container`.
+ containerMap, errs := imageContainersMap(runtime)
+ if len(containerMap) == 0 {
+ return nil, errs
+ }
+
+ // Create a map from `image ID -> *image.Image` for image lookups.
+ imagesSlice, err := runtime.ImageRuntime().GetImages()
+ if err != nil {
+ return nil, []error{err}
+ }
+ imageMap := make(map[string]*image.Image)
+ for i := range imagesSlice {
+ imageMap[imagesSlice[i].ID()] = imagesSlice[i]
+ }
+
+ // Connect to DBUS.
+ conn, err := systemd.ConnectToDBUS()
+ if err != nil {
+ logrus.Errorf(err.Error())
+ return nil, []error{err}
+ }
+ defer conn.Close()
+
+ // Update images.
+ containersToRestart := []*libpod.Container{}
+ updatedRawImages := make(map[string]bool)
+ for imageID, containers := range containerMap {
+ image, exists := imageMap[imageID]
+ if !exists {
+ errs = append(errs, errors.Errorf("container image ID %q not found in local storage", imageID))
+ return nil, errs
+ }
+ // Now we have to check if the image of any containers must be updated.
+ // Note that the image ID is NOT enough for this check as a given image
+ // may have multiple tags.
+ for i, ctr := range containers {
+ rawImageName := ctr.RawImageName()
+ if rawImageName == "" {
+ errs = append(errs, errors.Errorf("error auto-updating container %q: raw-image name is empty", ctr.ID()))
+ }
+ needsUpdate, err := newerImageAvailable(runtime, image, rawImageName)
+ if err != nil {
+ errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", ctr.ID(), rawImageName))
+ continue
+ }
+ if !needsUpdate {
+ continue
+ }
+ logrus.Infof("Auto-updating container %q using image %q", ctr.ID(), rawImageName)
+ if _, updated := updatedRawImages[rawImageName]; !updated {
+ _, err = updateImage(runtime, rawImageName)
+ if err != nil {
+ errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image update for %q failed", ctr.ID(), rawImageName))
+ continue
+ }
+ updatedRawImages[rawImageName] = true
+ }
+ containersToRestart = append(containersToRestart, containers[i])
+ }
+ }
+
+ // Restart containers.
+ updatedUnits := []string{}
+ for _, ctr := range containersToRestart {
+ labels := ctr.Labels()
+ unit, exists := labels[systemdGen.EnvVariable]
+ if !exists {
+ // Shouldn't happen but let's be sure of it.
+ errs = append(errs, errors.Errorf("error auto-updating container %q: no %s label found", ctr.ID(), systemdGen.EnvVariable))
+ continue
+ }
+ _, err := conn.RestartUnit(unit, "replace", nil)
+ if err != nil {
+ errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: restarting systemd unit %q failed", ctr.ID(), unit))
+ continue
+ }
+ logrus.Infof("Successfully restarted systemd unit %q", unit)
+ updatedUnits = append(updatedUnits, unit)
+ }
+
+ return updatedUnits, errs
+}
+
+// imageContainersMap generates a map[image ID] -> [containers using the image]
+// of all containers with a valid auto-update policy.
+func imageContainersMap(runtime *libpod.Runtime) (map[string][]*libpod.Container, []error) {
+ allContainers, err := runtime.GetAllContainers()
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ errors := []error{}
+ imageMap := make(map[string][]*libpod.Container)
+ for i, ctr := range allContainers {
+ state, err := ctr.State()
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+ // Only update running containers.
+ if state != define.ContainerStateRunning {
+ continue
+ }
+ // Only update containers with the specific label/policy set.
+ labels := ctr.Labels()
+ if value, exists := labels[Label]; exists {
+ policy, err := LookupPolicy(value)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+ if policy != PolicyNewImage {
+ continue
+ }
+ }
+ // Now we know that `ctr` is configured for auto updates.
+ id, _ := ctr.Image()
+ imageMap[id] = append(imageMap[id], allContainers[i])
+ }
+
+ return imageMap, errors
+}
+
+// newerImageAvailable returns true if there corresponding image on the remote
+// registry is newer.
+func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string) (bool, error) {
+ remoteRef, err := docker.ParseReference("//" + origName)
+ if err != nil {
+ return false, err
+ }
+
+ remoteImg, err := remoteRef.NewImage(context.Background(), runtime.SystemContext())
+ if err != nil {
+ return false, err
+ }
+
+ rawManifest, _, err := remoteImg.Manifest(context.Background())
+ if err != nil {
+ return false, err
+ }
+
+ remoteDigest, err := manifest.Digest(rawManifest)
+ if err != nil {
+ return false, err
+ }
+
+ return img.Digest().String() != remoteDigest.String(), nil
+}
+
+// updateImage pulls the specified image.
+func updateImage(runtime *libpod.Runtime, name string) (*image.Image, error) {
+ sys := runtime.SystemContext()
+ registryOpts := image.DockerRegistryOptions{}
+ signaturePolicyPath := ""
+ authFilePath := ""
+
+ if sys != nil {
+ registryOpts.OSChoice = sys.OSChoice
+ registryOpts.ArchitectureChoice = sys.OSChoice
+ registryOpts.DockerCertPath = sys.DockerCertPath
+
+ signaturePolicyPath = sys.SignaturePolicyPath
+ authFilePath = sys.AuthFilePath
+ }
+
+ newImage, err := runtime.ImageRuntime().New(context.Background(),
+ docker.Transport.Name()+"://"+name,
+ signaturePolicyPath,
+ authFilePath,
+ os.Stderr,
+ &registryOpts,
+ image.SigningOptions{},
+ nil,
+ util.PullImageAlways,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return newImage, nil
+}
diff --git a/pkg/autoupdate/autoupdate_test.go b/pkg/autoupdate/autoupdate_test.go
new file mode 100644
index 000000000..7a5da5bb0
--- /dev/null
+++ b/pkg/autoupdate/autoupdate_test.go
@@ -0,0 +1,50 @@
+package autoupdate
+
+import (
+ "testing"
+)
+
+func TestValidateImageReference(t *testing.T) {
+ tests := []struct {
+ input string
+ valid bool
+ }{
+ { // Fully-qualified reference
+ input: "quay.io/foo/bar:tag",
+ valid: true,
+ },
+ { // Fully-qualified reference in transport notation
+ input: "docker://quay.io/foo/bar:tag",
+ valid: true,
+ },
+ { // Fully-qualified reference but with digest
+ input: "quay.io/foo/bar@sha256:c9b1b535fdd91a9855fb7f82348177e5f019329a58c53c47272962dd60f71fc9",
+ valid: false,
+ },
+ { // Reference with missing tag
+ input: "quay.io/foo/bar",
+ valid: false,
+ },
+ { // Short name
+ input: "alpine",
+ valid: false,
+ },
+ { // Short name with repo
+ input: "library/alpine",
+ valid: false,
+ },
+ { // Wrong transport
+ input: "docker-archive:/some/path.tar",
+ valid: false,
+ },
+ }
+
+ for _, test := range tests {
+ err := ValidateImageReference(test.input)
+ if test.valid && err != nil {
+ t.Fatalf("parsing %q should have succeeded: %v", test.input, err)
+ } else if !test.valid && err == nil {
+ t.Fatalf("parsing %q should have failed", test.input)
+ }
+ }
+}
diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go
index e83c4a5e1..4b07847d1 100644
--- a/pkg/bindings/bindings.go
+++ b/pkg/bindings/bindings.go
@@ -7,3 +7,12 @@
// is established, users can then manage the Podman container runtime.
package bindings
+
+var (
+ // PTrue is a convenience variable that can be used in bindings where
+ // a pointer to a bool (optional parameter) is required.
+ PTrue bool = true
+ // PFalse is a convenience variable that can be used in bindings where
+ // a pointer to a bool (optional parameter) is required.
+ PFalse bool = false
+)
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index ba5f9c3aa..4fe4dd72d 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -109,7 +109,7 @@ func NewConnection(ctx context.Context, uri string, identity ...string) (context
}
client, err = tcpClient(_url)
default:
- return nil, errors.Errorf("%s is not a support schema", _url.Scheme)
+ return nil, errors.Errorf("'%s' is not a supported schema", _url.Scheme)
}
if err != nil {
return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme)
@@ -165,8 +165,13 @@ func sshClient(_url *url.URL, identity string, secure bool) (*http.Client, error
}
}
+ port := _url.Port()
+ if port == "" {
+ port = "22"
+ }
+
bastion, err := ssh.Dial("tcp",
- net.JoinHostPort(_url.Hostname(), _url.Port()),
+ net.JoinHostPort(_url.Hostname(), port),
&ssh.ClientConfig{
User: _url.User.Username(),
Auth: []ssh.AuthMethod{auth},
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index 670321f21..534555a00 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -7,6 +7,7 @@ import (
"strconv"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
lpapiv2 "github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/containers/libpod/pkg/bindings"
)
@@ -139,7 +140,6 @@ func Kill(ctx context.Context, nameOrID string, signal string) error {
return response.Process(nil)
}
-func Logs() {}
// Pause pauses a given container. The nameOrID can be a container name
// or a partial/full ID.
@@ -213,7 +213,7 @@ func Unpause(ctx context.Context, nameOrID string) error {
// Wait blocks until the given container reaches a condition. If not provided, the condition will
// default to stopped. If the condition is stopped, an exit code for the container will be provided. The
// nameOrID can be a container name or a partial/full ID.
-func Wait(ctx context.Context, nameOrID string, condition *string) (int32, error) {
+func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatus) (int32, error) { //nolint
var exitCode int32
conn, err := bindings.GetClient(ctx)
if err != nil {
@@ -221,7 +221,7 @@ func Wait(ctx context.Context, nameOrID string, condition *string) (int32, error
}
params := url.Values{}
if condition != nil {
- params.Set("condition", *condition)
+ params.Set("condition", condition.String())
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nameOrID)
if err != nil {
diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go
new file mode 100644
index 000000000..b7ecb3c7e
--- /dev/null
+++ b/pkg/bindings/containers/logs.go
@@ -0,0 +1,116 @@
+package containers
+
+import (
+ "context"
+ "encoding/binary"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/pkg/errors"
+)
+
+// Logs obtains a container's logs given the options provided. The logs are then sent to the
+// stdout|stderr channels as strings.
+func Logs(ctx context.Context, nameOrID string, opts LogOptions, stdoutChan, stderrChan chan string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if opts.Follow != nil {
+ params.Set("follow", strconv.FormatBool(*opts.Follow))
+ }
+ if opts.Since != nil {
+ params.Set("since", *opts.Since)
+ }
+ if opts.Stderr != nil {
+ params.Set("stderr", strconv.FormatBool(*opts.Stderr))
+ }
+ if opts.Stdout != nil {
+ params.Set("stdout", strconv.FormatBool(*opts.Stdout))
+ }
+ if opts.Tail != nil {
+ params.Set("tail", *opts.Tail)
+ }
+ if opts.Timestamps != nil {
+ params.Set("timestamps", strconv.FormatBool(*opts.Timestamps))
+ }
+ if opts.Until != nil {
+ params.Set("until", *opts.Until)
+ }
+ // The API requires either stdout|stderr be used. If neither are specified, we specify stdout
+ if opts.Stdout == nil && opts.Stderr == nil {
+ params.Set("stdout", strconv.FormatBool(true))
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nameOrID)
+ if err != nil {
+ return err
+ }
+
+ // read 8 bytes
+ // first byte determines stderr=2|stdout=1
+ // bytes 4-7 len(msg) in uint32
+ for {
+ stream, msgSize, err := readHeader(response.Body)
+ if err != nil {
+ // In case the server side closes up shop because !follow
+ if err == io.EOF {
+ break
+ }
+ return errors.Wrap(err, "unable to read log header")
+ }
+ msg, err := readMsg(response.Body, msgSize)
+ if err != nil {
+ return errors.Wrap(err, "unable to read log message")
+ }
+ if stream == 1 {
+ stdoutChan <- msg
+ } else {
+ stderrChan <- msg
+ }
+ }
+ return nil
+}
+
+func readMsg(r io.Reader, msgSize int) (string, error) {
+ var msg []byte
+ size := msgSize
+ for {
+ b := make([]byte, size)
+ _, err := r.Read(b)
+ if err != nil {
+ return "", err
+ }
+ msg = append(msg, b...)
+ if len(msg) == msgSize {
+ break
+ }
+ size = msgSize - len(msg)
+ }
+ return string(msg), nil
+}
+
+func readHeader(r io.Reader) (byte, int, error) {
+ var (
+ header []byte
+ size = 8
+ )
+ for {
+ b := make([]byte, size)
+ _, err := r.Read(b)
+ if err != nil {
+ return 0, 0, err
+ }
+ header = append(header, b...)
+ if len(header) == 8 {
+ break
+ }
+ size = 8 - len(header)
+ }
+ stream := header[0]
+ msgSize := int(binary.BigEndian.Uint32(header[4:]) - 8)
+ return stream, msgSize, nil
+}
diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go
new file mode 100644
index 000000000..87342f7ea
--- /dev/null
+++ b/pkg/bindings/containers/types.go
@@ -0,0 +1,13 @@
+package containers
+
+// LogOptions describe finer control of log content or
+// how the content is formatted.
+type LogOptions struct {
+ Follow *bool
+ Since *string
+ Stderr *bool
+ Stdout *bool
+ Tail *string
+ Timestamps *bool
+ Until *string
+}
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
new file mode 100644
index 000000000..a8d1e6ca3
--- /dev/null
+++ b/pkg/bindings/manifests/manifests.go
@@ -0,0 +1,126 @@
+package manifests
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ jsoniter "github.com/json-iterator/go"
+)
+
+// Create creates a manifest for the given name. Optional images to be associated with
+// the new manifest can also be specified. The all boolean specifies to add all entries
+// of a list if the name provided is a manifest list. The ID of the new manifest list
+// is returned as a string.
+func Create(ctx context.Context, names, images []string, all *bool) (string, error) {
+ var idr handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ if len(names) < 1 {
+ return "", errors.New("creating a manifest requires at least one name argument")
+ }
+ params := url.Values{}
+ if all != nil {
+ params.Set("all", strconv.FormatBool(*all))
+ }
+ for _, name := range names {
+ params.Add("name", name)
+ }
+ for _, i := range images {
+ params.Add("image", i)
+ }
+
+ response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
+
+// Inspect returns a manifest list for a given name.
+func Inspect(ctx context.Context, name string) (*manifest.Schema2List, error) {
+ var list manifest.Schema2List
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, name)
+ if err != nil {
+ return nil, err
+ }
+ return &list, response.Process(&list)
+}
+
+// Add adds a manifest to a given manifest list. Additional options for the manifest
+// can also be specified. The ID of the new manifest list is returned as a string
+func Add(ctx context.Context, name string, options image.ManifestAddOpts) (string, error) {
+ var idr handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ optionsString, err := jsoniter.MarshalToString(options)
+ if err != nil {
+ return "", err
+ }
+ stringReader := strings.NewReader(optionsString)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, name)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
+
+// Remove deletes a manifest entry from a manifest list. Both name and the digest to be
+// removed are mandatory inputs. The ID of the new manifest list is returned as a string.
+func Remove(ctx context.Context, name, digest string) (string, error) {
+ var idr handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ params.Set("digest", digest)
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, name)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
+
+// Push takes a manifest list and pushes to a destination. If the destination is not specified,
+// the name will be used instead. If the optional all boolean is specified, all images specified
+// in the list will be pushed as well.
+func Push(ctx context.Context, name string, destination *string, all *bool) (string, error) {
+ var (
+ idr handlers.IDResponse
+ )
+ dest := name
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ params.Set("image", name)
+ if destination != nil {
+ dest = name
+ }
+ params.Set("destination", dest)
+ if all != nil {
+ params.Set("all", strconv.FormatBool(*all))
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, name)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go
new file mode 100644
index 000000000..fce8bbb8e
--- /dev/null
+++ b/pkg/bindings/system/system.go
@@ -0,0 +1,61 @@
+package system
+
+import (
+ "context"
+ "encoding/json"
+ "io"
+ "net/http"
+ "net/url"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// Events allows you to monitor libdpod related events like container creation and
+// removal. The events are then passed to the eventChan provided. The optional cancelChan
+// can be used to cancel the read of events and close down the HTTP connection.
+func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if since != nil {
+ params.Set("since", *since)
+ }
+ if until != nil {
+ params.Set("until", *until)
+ }
+ if filters != nil {
+ filterString, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return errors.Wrap(err, "invalid filters")
+ }
+ params.Set("filters", filterString)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/events", params)
+ if err != nil {
+ return err
+ }
+ if cancelChan != nil {
+ go func() {
+ <-cancelChan
+ err = response.Body.Close()
+ logrus.Error(errors.Wrap(err, "unable to close event response body"))
+ }()
+ }
+ dec := json.NewDecoder(response.Body)
+ for {
+ e := handlers.Event{}
+ if err := dec.Decode(&e); err != nil {
+ if err == io.EOF {
+ break
+ }
+ return errors.Wrap(err, "unable to decode event response")
+ }
+ eventChan <- e
+ }
+ return nil
+}
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index a4d065a14..6b8d6788c 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -3,6 +3,7 @@ package test_bindings
import (
"context"
"fmt"
+ "github.com/containers/libpod/libpod/define"
"io/ioutil"
"os"
"os/exec"
@@ -152,7 +153,7 @@ func (b *bindingTest) startAPIService() *gexec.Session {
var (
cmd []string
)
- cmd = append(cmd, "--log-level=debug", "system", "service", "--timeout=999999", b.sock)
+ cmd = append(cmd, "--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock)
return b.runPodman(cmd)
}
@@ -205,8 +206,8 @@ func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, po
if err != nil {
return "", err
}
- waiting := "running"
- _, err = containers.Wait(b.conn, ctr.ID, &waiting)
+ wait := define.ContainerStateRunning
+ _, err = containers.Wait(b.conn, ctr.ID, &wait)
return ctr.ID, err
}
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index 34a9c3136..f5465c803 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -1,12 +1,15 @@
package test_bindings
import (
+ "github.com/containers/libpod/libpod/define"
"net/http"
"strconv"
+ "strings"
"time"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -15,11 +18,9 @@ import (
var _ = Describe("Podman containers ", func() {
var (
- bt *bindingTest
- s *gexec.Session
- err error
- falseFlag bool = false
- trueFlag bool = true
+ bt *bindingTest
+ s *gexec.Session
+ err error
)
BeforeEach(func() {
@@ -55,7 +56,7 @@ var _ = Describe("Podman containers ", func() {
It("podman pause a running container by name", func() {
// Pausing by name should work
var name = "top"
- _, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -69,7 +70,7 @@ var _ = Describe("Podman containers ", func() {
It("podman pause a running container by id", func() {
// Pausing by id should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
@@ -83,7 +84,7 @@ var _ = Describe("Podman containers ", func() {
It("podman unpause a running container by name", func() {
// Unpausing by name should work
var name = "top"
- _, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -99,7 +100,7 @@ var _ = Describe("Podman containers ", func() {
It("podman unpause a running container by ID", func() {
// Unpausing by ID should work
var name = "top"
- _, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
// Pause by name
err = containers.Pause(bt.conn, name)
@@ -118,7 +119,7 @@ var _ = Describe("Podman containers ", func() {
It("podman pause a paused container by name", func() {
// Pausing a paused container by name should fail
var name = "top"
- _, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -131,7 +132,7 @@ var _ = Describe("Podman containers ", func() {
It("podman pause a paused container by id", func() {
// Pausing a paused container by id should fail
var name = "top"
- cid, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
@@ -144,7 +145,7 @@ var _ = Describe("Podman containers ", func() {
It("podman pause a stopped container by name", func() {
// Pausing a stopped container by name should fail
var name = "top"
- _, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
@@ -157,7 +158,7 @@ var _ = Describe("Podman containers ", func() {
It("podman pause a stopped container by id", func() {
// Pausing a stopped container by id should fail
var name = "top"
- cid, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, cid, nil)
Expect(err).To(BeNil())
@@ -170,11 +171,11 @@ var _ = Describe("Podman containers ", func() {
It("podman remove a paused container by id without force", func() {
// Removing a paused container without force should fail
var name = "top"
- cid, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
- err = containers.Remove(bt.conn, cid, &falseFlag, &falseFlag)
+ err = containers.Remove(bt.conn, cid, &bindings.PFalse, &bindings.PFalse)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
@@ -191,18 +192,18 @@ var _ = Describe("Podman containers ", func() {
// Removing a paused container with force should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
- err = containers.Remove(bt.conn, cid, &trueFlag, &falseFlag)
+ err = containers.Remove(bt.conn, cid, &bindings.PTrue, &bindings.PFalse)
Expect(err).To(BeNil())
})
It("podman stop a paused container by name", func() {
// Stopping a paused container by name should fail
var name = "top"
- _, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -215,7 +216,7 @@ var _ = Describe("Podman containers ", func() {
It("podman stop a paused container by id", func() {
// Stopping a paused container by id should fail
var name = "top"
- cid, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
@@ -228,7 +229,7 @@ var _ = Describe("Podman containers ", func() {
It("podman stop a running container by name", func() {
// Stopping a running container by name should work
var name = "top"
- _, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
@@ -242,7 +243,7 @@ var _ = Describe("Podman containers ", func() {
It("podman stop a running container by ID", func() {
// Stopping a running container by ID should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &falseFlag, nil)
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, cid, nil)
Expect(err).To(BeNil())
@@ -282,8 +283,8 @@ var _ = Describe("Podman containers ", func() {
var (
name = "top"
exitCode int32 = -1
- pause = "paused"
- unpause = "running"
+ pause = define.ContainerStatePaused
+ running = define.ContainerStateRunning
)
errChan := make(chan error)
_, err := bt.RunTopContainer(&name, nil, nil)
@@ -301,8 +302,8 @@ var _ = Describe("Podman containers ", func() {
errChan = make(chan error)
go func() {
- exitCode, err = containers.Wait(bt.conn, name, &unpause)
- errChan <- err
+ _, waitErr := containers.Wait(bt.conn, name, &running)
+ errChan <- waitErr
close(errChan)
}()
err = containers.Unpause(bt.conn, name)
@@ -323,7 +324,7 @@ var _ = Describe("Podman containers ", func() {
// a container that has no healthcheck should be a 409
var name = "top"
- bt.RunTopContainer(&name, &falseFlag, nil)
+ bt.RunTopContainer(&name, &bindings.PFalse, nil)
_, err = containers.RunHealthCheck(bt.conn, name)
Expect(err).ToNot(BeNil())
code, _ = bindings.CheckResponseCode(err)
@@ -357,4 +358,26 @@ var _ = Describe("Podman containers ", func() {
//Expect(code).To(BeNumerically("==", http.StatusConflict))
})
+ It("logging", func() {
+ stdoutChan := make(chan string, 10)
+ s := specgen.NewSpecGenerator(alpine.name)
+ s.Terminal = true
+ s.Command = []string{"date", "-R"}
+ r, err := containers.CreateWithSpec(bt.conn, s)
+ Expect(err).To(BeNil())
+ err = containers.Start(bt.conn, r.ID, nil)
+ Expect(err).To(BeNil())
+
+ _, err = containers.Wait(bt.conn, r.ID, nil)
+ Expect(err).To(BeNil())
+
+ opts := containers.LogOptions{Stdout: &bindings.PTrue, Follow: &bindings.PTrue}
+ go func() {
+ containers.Logs(bt.conn, r.ID, opts, stdoutChan, nil)
+ }()
+ o := <-stdoutChan
+ o = strings.ReplaceAll(o, "\r", "")
+ _, err = time.Parse(time.RFC1123Z, o)
+ Expect(err).To(BeNil())
+ })
})
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
index 17b3b254a..5e4cfe7be 100644
--- a/pkg/bindings/test/images_test.go
+++ b/pkg/bindings/test/images_test.go
@@ -19,11 +19,9 @@ var _ = Describe("Podman images", func() {
//tempdir string
//err error
//podmanTest *PodmanTestIntegration
- bt *bindingTest
- s *gexec.Session
- err error
- falseFlag bool = false
- trueFlag bool = true
+ bt *bindingTest
+ s *gexec.Session
+ err error
)
BeforeEach(func() {
@@ -76,7 +74,7 @@ var _ = Describe("Podman images", func() {
//Expect(data.Size).To(BeZero())
// Enabling the size parameter should result in size being populated
- data, err = images.GetImage(bt.conn, alpine.name, &trueFlag)
+ data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue)
Expect(err).To(BeNil())
Expect(data.Size).To(BeNumerically(">", 0))
})
@@ -84,7 +82,7 @@ var _ = Describe("Podman images", func() {
// Test to validate the remove image api
It("remove image", func() {
// Remove invalid image should be a 404
- _, err = images.Remove(bt.conn, "foobar5000", &falseFlag)
+ _, err = images.Remove(bt.conn, "foobar5000", &bindings.PFalse)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
@@ -101,21 +99,21 @@ var _ = Describe("Podman images", func() {
// Start a container with alpine image
var top string = "top"
- _, err = bt.RunTopContainer(&top, &falseFlag, nil)
+ _, err = bt.RunTopContainer(&top, &bindings.PFalse, nil)
Expect(err).To(BeNil())
// we should now have a container called "top" running
- containerResponse, err := containers.Inspect(bt.conn, "top", &falseFlag)
+ containerResponse, err := containers.Inspect(bt.conn, "top", &bindings.PFalse)
Expect(err).To(BeNil())
Expect(containerResponse.Name).To(Equal("top"))
// try to remove the image "alpine". This should fail since we are not force
// deleting hence image cannot be deleted until the container is deleted.
- response, err = images.Remove(bt.conn, alpine.shortName, &falseFlag)
+ response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PFalse)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
// Removing the image "alpine" where force = true
- response, err = images.Remove(bt.conn, alpine.shortName, &trueFlag)
+ response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PTrue)
Expect(err).To(BeNil())
// Checking if both the images are gone as well as the container is deleted
@@ -127,7 +125,7 @@ var _ = Describe("Podman images", func() {
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
- _, err = containers.Inspect(bt.conn, "top", &falseFlag)
+ _, err = containers.Inspect(bt.conn, "top", &bindings.PFalse)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
})
@@ -178,13 +176,13 @@ var _ = Describe("Podman images", func() {
// List images with a filter
filters := make(map[string][]string)
filters["reference"] = []string{alpine.name}
- filteredImages, err := images.List(bt.conn, &falseFlag, filters)
+ filteredImages, err := images.List(bt.conn, &bindings.PFalse, filters)
Expect(err).To(BeNil())
Expect(len(filteredImages)).To(BeNumerically("==", 1))
// List images with a bad filter
filters["name"] = []string{alpine.name}
- _, err = images.List(bt.conn, &falseFlag, filters)
+ _, err = images.List(bt.conn, &bindings.PFalse, filters)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
new file mode 100644
index 000000000..23c3d8194
--- /dev/null
+++ b/pkg/bindings/test/manifests_test.go
@@ -0,0 +1,124 @@
+package test_bindings
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/images"
+ "github.com/containers/libpod/pkg/bindings/manifests"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman containers ", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ )
+
+ BeforeEach(func() {
+ bt = newBindingTest()
+ bt.RestoreImagesFromCache()
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ err := bt.NewConnection()
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("create manifest", func() {
+ // create manifest list without images
+ id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ Expect(err).To(BeNil())
+ list, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(list.Manifests)).To(BeZero())
+
+ // creating a duplicate should fail as a 500
+ _, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+
+ _, err = images.Remove(bt.conn, id, nil)
+ Expect(err).To(BeNil())
+
+ // create manifest list with images
+ id, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
+ Expect(err).To(BeNil())
+ list, err = manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+ })
+
+ It("inspect bogus manifest", func() {
+ _, err := manifests.Inspect(bt.conn, "larry")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+ It("add manifest", func() {
+ // add to bogus should 404
+ _, err := manifests.Add(bt.conn, "foobar", image.ManifestAddOpts{})
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ Expect(err).To(BeNil())
+ opts := image.ManifestAddOpts{Images: []string{alpine.name}}
+ _, err = manifests.Add(bt.conn, id, opts)
+ Expect(err).To(BeNil())
+ list, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+
+ // add bogus name to existing list should fail
+ opts.Images = []string{"larry"}
+ _, err = manifests.Add(bt.conn, id, opts)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("remove manifest", func() {
+ // removal on bogus manifest list should be 404
+ _, err := manifests.Remove(bt.conn, "larry", "1234")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
+ Expect(err).To(BeNil())
+ data, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(data.Manifests)).To(BeNumerically("==", 1))
+
+ // removal on a good manifest list with a bad digest should be 400
+ _, err = manifests.Remove(bt.conn, id, "!234")
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusBadRequest))
+
+ digest := data.Manifests[0].Digest.String()
+ _, err = manifests.Remove(bt.conn, id, digest)
+ Expect(err).To(BeNil())
+
+ // removal on good manifest with good digest should work
+ data, err = manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(data.Manifests)).To(BeZero())
+ })
+
+ It("push manifest", func() {
+ Skip("TODO")
+ })
+})
diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go
index 7e29265b7..bcf8e69b8 100644
--- a/pkg/bindings/test/pods_test.go
+++ b/pkg/bindings/test/pods_test.go
@@ -14,11 +14,10 @@ import (
var _ = Describe("Podman pods", func() {
var (
- bt *bindingTest
- s *gexec.Session
- newpod string
- err error
- trueFlag bool = true
+ bt *bindingTest
+ s *gexec.Session
+ newpod string
+ err error
)
BeforeEach(func() {
@@ -57,7 +56,7 @@ var _ = Describe("Podman pods", func() {
Expect(err).To(BeNil())
Expect(len(podSummary)).To(Equal(1))
// Adding an alpine container to the existing pod
- _, err = bt.RunTopContainer(nil, &trueFlag, &newpod)
+ _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
Expect(err).To(BeNil())
podSummary, err = pods.List(bt.conn, nil)
// Verify no errors.
@@ -76,15 +75,66 @@ var _ = Describe("Podman pods", func() {
}
Expect(StringInSlice(newpod, names)).To(BeTrue())
Expect(StringInSlice("newpod2", names)).To(BeTrue())
+ })
+
+ // The test validates the list pod endpoint with passing filters as the params.
+ It("List pods with filters", func() {
+ var newpod2 string = "newpod2"
+ bt.Podcreate(&newpod2)
+ _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
+ Expect(err).To(BeNil())
+
+ // Expected err with invalid filter params
+ filters := make(map[string][]string)
+ filters["dummy"] = []string{"dummy"}
+ filteredPods, err := pods.List(bt.conn, filters)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+
+ // Expected empty response with invalid filters
+ filters = make(map[string][]string)
+ filters["name"] = []string{"dummy"}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 0))
+
+ // Validate list pod with name filter
+ filters = make(map[string][]string)
+ filters["name"] = []string{newpod2}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ var names []string
+ for _, i := range filteredPods {
+ names = append(names, i.Config.Name)
+ }
+ Expect(StringInSlice("newpod2", names)).To(BeTrue())
+
+ // Validate list pod with id filter
+ filters = make(map[string][]string)
+ response, err := pods.Inspect(bt.conn, newpod)
+ id := response.Config.ID
+ filters["id"] = []string{id}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ names = names[:0]
+ for _, i := range filteredPods {
+ names = append(names, i.Config.Name)
+ }
+ Expect(StringInSlice("newpod", names)).To(BeTrue())
- // TODO 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(bt.conn, filters)
- //Expect(err).To(BeNil())
- //Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ // Using multiple filters
+ filters["name"] = []string{newpod}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ names = names[:0]
+ for _, i := range filteredPods {
+ names = append(names, i.Config.Name)
+ }
+ Expect(StringInSlice("newpod", names)).To(BeTrue())
})
// The test validates if the exists responds
@@ -111,7 +161,7 @@ var _ = Describe("Podman pods", func() {
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Adding an alpine container to the existing pod
- _, err = bt.RunTopContainer(nil, &trueFlag, &newpod)
+ _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
Expect(err).To(BeNil())
// Binding needs to be modified to inspect the pod state.
diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go
new file mode 100644
index 000000000..3abc26b34
--- /dev/null
+++ b/pkg/bindings/test/system_test.go
@@ -0,0 +1,51 @@
+package test_bindings
+
+import (
+ "time"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings/system"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman system", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ )
+
+ BeforeEach(func() {
+ bt = newBindingTest()
+ bt.RestoreImagesFromCache()
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ err := bt.NewConnection()
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("podman events", func() {
+ eChan := make(chan handlers.Event, 1)
+ var messages []handlers.Event
+ cancelChan := make(chan bool, 1)
+ go func() {
+ for e := range eChan {
+ messages = append(messages, e)
+ }
+ }()
+ go func() {
+ system.Events(bt.conn, eChan, cancelChan, nil, nil, nil)
+ }()
+
+ _, err := bt.RunTopContainer(nil, nil, nil)
+ Expect(err).To(BeNil())
+ cancelChan <- true
+ Expect(len(messages)).To(BeNumerically("==", 3))
+ })
+})
diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go
index c8940d46e..9da034d24 100644
--- a/pkg/bindings/test/volumes_test.go
+++ b/pkg/bindings/test/volumes_test.go
@@ -3,13 +3,13 @@ package test_bindings
import (
"context"
"fmt"
- "github.com/containers/libpod/pkg/api/handlers"
- "github.com/containers/libpod/pkg/bindings/containers"
- "github.com/containers/libpod/pkg/bindings/volumes"
"net/http"
"time"
"github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/bindings/volumes"
+ "github.com/containers/libpod/pkg/domain/entities"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
@@ -24,7 +24,6 @@ var _ = Describe("Podman volumes", func() {
s *gexec.Session
connText context.Context
err error
- trueFlag = true
)
BeforeEach(func() {
@@ -53,13 +52,13 @@ var _ = Describe("Podman volumes", func() {
It("create volume", func() {
// create a volume with blank config should work
- _, err := volumes.Create(connText, handlers.VolumeCreateConfig{})
+ _, err := volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
- vcc := handlers.VolumeCreateConfig{
- Name: "foobar",
- Label: nil,
- Opts: nil,
+ vcc := entities.VolumeCreateOptions{
+ Name: "foobar",
+ Label: nil,
+ Options: nil,
}
vol, err := volumes.Create(connText, vcc)
Expect(err).To(BeNil())
@@ -73,7 +72,7 @@ var _ = Describe("Podman volumes", func() {
})
It("inspect volume", func() {
- vol, err := volumes.Create(connText, handlers.VolumeCreateConfig{})
+ vol, err := volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
data, err := volumes.Inspect(connText, vol.Name)
Expect(err).To(BeNil())
@@ -87,13 +86,13 @@ var _ = Describe("Podman volumes", func() {
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Removing an unused volume should work
- vol, err := volumes.Create(connText, handlers.VolumeCreateConfig{})
+ vol, err := volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
err = volumes.Remove(connText, vol.Name, nil)
Expect(err).To(BeNil())
// Removing a volume that is being used without force should be 409
- vol, err = volumes.Create(connText, handlers.VolumeCreateConfig{})
+ vol, err = volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/foobar", vol.Name), "--name", "vtest", alpine.name, "top"})
session.Wait(45)
@@ -106,7 +105,7 @@ var _ = Describe("Podman volumes", func() {
zero := 0
err = containers.Stop(connText, "vtest", &zero)
Expect(err).To(BeNil())
- err = volumes.Remove(connText, vol.Name, &trueFlag)
+ err = volumes.Remove(connText, vol.Name, &bindings.PTrue)
Expect(err).To(BeNil())
})
@@ -119,7 +118,7 @@ var _ = Describe("Podman volumes", func() {
// create a bunch of named volumes and make verify with list
volNames := []string{"homer", "bart", "lisa", "maggie", "marge"}
for i := 0; i < 5; i++ {
- _, err = volumes.Create(connText, handlers.VolumeCreateConfig{Name: volNames[i]})
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{Name: volNames[i]})
Expect(err).To(BeNil())
}
vols, err = volumes.List(connText, nil)
@@ -152,15 +151,15 @@ var _ = Describe("Podman volumes", func() {
Expect(err).To(BeNil())
// Removing an unused volume should work
- _, err = volumes.Create(connText, handlers.VolumeCreateConfig{})
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
vols, err := volumes.Prune(connText)
Expect(err).To(BeNil())
Expect(len(vols)).To(BeNumerically("==", 1))
- _, err = volumes.Create(connText, handlers.VolumeCreateConfig{Name: "homer"})
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{Name: "homer"})
Expect(err).To(BeNil())
- _, err = volumes.Create(connText, handlers.VolumeCreateConfig{})
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"})
session.Wait(45)
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
index 0bc818605..a2164e0af 100644
--- a/pkg/bindings/volumes/volumes.go
+++ b/pkg/bindings/volumes/volumes.go
@@ -8,15 +8,15 @@ import (
"strings"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
jsoniter "github.com/json-iterator/go"
)
// Create creates a volume given its configuration.
-func Create(ctx context.Context, config handlers.VolumeCreateConfig) (*libpod.VolumeConfig, error) {
+func Create(ctx context.Context, config entities.VolumeCreateOptions) (*entities.VolumeConfigResponse, error) {
var (
- v libpod.VolumeConfig
+ v entities.VolumeConfigResponse
)
conn, err := bindings.GetClient(ctx)
if err != nil {
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
new file mode 100644
index 000000000..0e1208b3b
--- /dev/null
+++ b/pkg/domain/entities/containers.go
@@ -0,0 +1,23 @@
+package entities
+
+import (
+ "time"
+
+ "github.com/containers/libpod/libpod/define"
+)
+
+type WaitOptions struct {
+ Condition define.ContainerStatus
+ Interval time.Duration
+ Latest bool
+}
+
+type WaitReport struct {
+ Id string
+ Error error
+ ExitCode int32
+}
+
+type BoolReport struct {
+ Value bool
+}
diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go
new file mode 100644
index 000000000..08ef1df92
--- /dev/null
+++ b/pkg/domain/entities/engine.go
@@ -0,0 +1,96 @@
+package entities
+
+import (
+ "os/user"
+ "path/filepath"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/spf13/pflag"
+)
+
+type EngineMode string
+
+const (
+ ABIMode = EngineMode("abi")
+ TunnelMode = EngineMode("tunnel")
+)
+
+func (m EngineMode) String() string {
+ return string(m)
+}
+
+// FIXME: merge EngineOptions and EngineFlags
+type EngineOptions struct {
+ Uri string
+ Identities []string
+ FlagSet *pflag.FlagSet
+ Flags EngineFlags
+ EngineMode EngineMode
+}
+
+type EngineFlags struct {
+ CGroupManager string
+ CniConfigDir string
+ ConmonPath string
+ DefaultMountsFile string
+ EventsBackend string
+ HooksDir []string
+ MaxWorks int
+ Namespace string
+ Root string
+ Runroot string
+ Runtime string
+ StorageDriver string
+ StorageOpts []string
+ Syslog bool
+ Trace bool
+ NetworkCmdPath string
+
+ Config string
+ CpuProfile string
+ LogLevel string
+ TmpDir string
+
+ RemoteUserName string
+ RemoteHost string
+ VarlinkAddress string
+ ConnectionName string
+ RemoteConfigFilePath string
+ Port int
+ IdentityFile string
+ IgnoreHosts bool
+}
+
+func NewEngineOptions() (EngineFlags, error) {
+ u, _ := user.Current()
+ return EngineFlags{
+ CGroupManager: define.SystemdCgroupsManager,
+ CniConfigDir: "",
+ Config: "",
+ ConmonPath: filepath.Join("usr", "bin", "conmon"),
+ ConnectionName: "",
+ CpuProfile: "",
+ DefaultMountsFile: "",
+ EventsBackend: "",
+ HooksDir: nil,
+ IdentityFile: "",
+ IgnoreHosts: false,
+ LogLevel: "",
+ MaxWorks: 0,
+ Namespace: "",
+ NetworkCmdPath: "",
+ Port: 0,
+ RemoteConfigFilePath: "",
+ RemoteHost: "",
+ RemoteUserName: "",
+ Root: "",
+ Runroot: filepath.Join("run", "user", u.Uid),
+ Runtime: "",
+ StorageDriver: "overlayfs",
+ StorageOpts: nil,
+ Syslog: false,
+ TmpDir: filepath.Join("run", "user", u.Uid, "libpod", "tmp"),
+ Trace: false,
+ VarlinkAddress: "",
+ }, nil
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
new file mode 100644
index 000000000..5820c12c3
--- /dev/null
+++ b/pkg/domain/entities/engine_container.go
@@ -0,0 +1,18 @@
+package entities
+
+import (
+ "context"
+)
+
+type ContainerEngine interface {
+ ContainerDelete(ctx context.Context, opts ContainerDeleteOptions) (*ContainerDeleteReport, error)
+ ContainerPrune(ctx context.Context) (*ContainerPruneReport, error)
+ ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
+ ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
+ PodDelete(ctx context.Context, opts PodPruneOptions) (*PodDeleteReport, error)
+ PodExists(ctx context.Context, nameOrId string) (*BoolReport, error)
+ PodPrune(ctx context.Context) (*PodPruneReport, error)
+ VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
+ VolumeDelete(ctx context.Context, opts VolumeDeleteOptions) (*VolumeDeleteReport, error)
+ VolumePrune(ctx context.Context) (*VolumePruneReport, error)
+}
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
new file mode 100644
index 000000000..27676d781
--- /dev/null
+++ b/pkg/domain/entities/engine_image.go
@@ -0,0 +1,12 @@
+package entities
+
+import (
+ "context"
+)
+
+type ImageEngine interface {
+ Delete(ctx context.Context, nameOrId string, opts ImageDeleteOptions) (*ImageDeleteReport, error)
+ History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
+ List(ctx context.Context, opts ImageListOptions) (*ImageListReport, error)
+ Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
+}
diff --git a/pkg/domain/entities/filters.go b/pkg/domain/entities/filters.go
new file mode 100644
index 000000000..c7e227244
--- /dev/null
+++ b/pkg/domain/entities/filters.go
@@ -0,0 +1,150 @@
+package entities
+
+import (
+ "net/url"
+ "strings"
+)
+
+// Identifier interface allows filters to access ID() of object
+type Identifier interface {
+ Id() string
+}
+
+// Named interface allows filters to access Name() of object
+type Named interface {
+ Name() string
+}
+
+// Named interface allows filters to access Name() of object
+type Names interface {
+ Names() []string
+}
+
+// IdOrName interface allows filters to access ID() or Name() of object
+type IdOrNamed interface {
+ Identifier
+ Named
+}
+
+// IdOrName interface allows filters to access ID() or Names() of object
+type IdOrNames interface {
+ Identifier
+ Names
+}
+
+type ImageFilter func(Image) bool
+type VolumeFilter func(Volume) bool
+type ContainerFilter func(Container) bool
+
+func CompileImageFilters(filters url.Values) ImageFilter {
+ var fns []interface{}
+
+ for name, targets := range filters {
+ switch name {
+ case "id":
+ fns = append(fns, FilterIdFn(targets))
+ case "name":
+ fns = append(fns, FilterNamesFn(targets))
+ case "idOrName":
+ fns = append(fns, FilterIdOrNameFn(targets))
+ }
+ }
+
+ return func(image Image) bool {
+ for _, fn := range fns {
+ if !fn.(ImageFilter)(image) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+func CompileContainerFilters(filters url.Values) ContainerFilter {
+ var fns []interface{}
+
+ for name, targets := range filters {
+ switch name {
+ case "id":
+ fns = append(fns, FilterIdFn(targets))
+ case "name":
+ fns = append(fns, FilterNameFn(targets))
+ case "idOrName":
+ fns = append(fns, FilterIdOrNameFn(targets))
+ }
+ }
+
+ return func(ctnr Container) bool {
+ for _, fn := range fns {
+ if !fn.(ContainerFilter)(ctnr) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+func CompileVolumeFilters(filters url.Values) VolumeFilter {
+ var fns []interface{}
+
+ for name, targets := range filters {
+ if name == "id" {
+ fns = append(fns, FilterIdFn(targets))
+ }
+ }
+
+ return func(volume Volume) bool {
+ for _, fn := range fns {
+ if !fn.(VolumeFilter)(volume) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+func FilterIdFn(id []string) func(Identifier) bool {
+ return func(obj Identifier) bool {
+ for _, v := range id {
+ if strings.Contains(obj.Id(), v) {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+func FilterNameFn(name []string) func(Named) bool {
+ return func(obj Named) bool {
+ for _, v := range name {
+ if strings.Contains(obj.Name(), v) {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+func FilterNamesFn(name []string) func(Names) bool {
+ return func(obj Names) bool {
+ for _, v := range name {
+ for _, n := range obj.Names() {
+ if strings.Contains(n, v) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+}
+
+func FilterIdOrNameFn(id []string) func(IdOrNamed) bool {
+ return func(obj IdOrNamed) bool {
+ for _, v := range id {
+ if strings.Contains(obj.Id(), v) || strings.Contains(obj.Name(), v) {
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
new file mode 100644
index 000000000..c84ed5351
--- /dev/null
+++ b/pkg/domain/entities/images.go
@@ -0,0 +1,151 @@
+package entities
+
+import (
+ "net/url"
+
+ "github.com/containers/image/v5/manifest"
+ docker "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/container"
+ "github.com/opencontainers/go-digest"
+ v1 "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+type Image struct {
+ IdOrNamed
+ ID string `json:"Id"`
+ RepoTags []string `json:",omitempty"`
+ RepoDigests []string `json:",omitempty"`
+ Parent string `json:",omitempty"`
+ Comment string `json:",omitempty"`
+ Created string `json:",omitempty"`
+ Container string `json:",omitempty"`
+ ContainerConfig *container.Config `json:",omitempty"`
+ DockerVersion string `json:",omitempty"`
+ Author string `json:",omitempty"`
+ Config *container.Config `json:",omitempty"`
+ Architecture string `json:",omitempty"`
+ Variant string `json:",omitempty"`
+ Os string `json:",omitempty"`
+ OsVersion string `json:",omitempty"`
+ Size int64 `json:",omitempty"`
+ VirtualSize int64 `json:",omitempty"`
+ GraphDriver docker.GraphDriverData `json:",omitempty"`
+ RootFS docker.RootFS `json:",omitempty"`
+ Metadata docker.ImageMetadata `json:",omitempty"`
+
+ // Podman extensions
+ Digest digest.Digest `json:",omitempty"`
+ PodmanVersion string `json:",omitempty"`
+ ManifestType string `json:",omitempty"`
+ User string `json:",omitempty"`
+ History []v1.History `json:",omitempty"`
+ NamesHistory []string `json:",omitempty"`
+ HealthCheck *manifest.Schema2HealthConfig `json:",omitempty"`
+}
+
+func (i *Image) Id() string {
+ return i.ID
+}
+
+type ImageSummary struct {
+ Identifier
+ ID string `json:"Id"`
+ ParentId string `json:",omitempty"`
+ RepoTags []string `json:",omitempty"`
+ Created int `json:",omitempty"`
+ Size int `json:",omitempty"`
+ SharedSize int `json:",omitempty"`
+ VirtualSize int `json:",omitempty"`
+ Labels string `json:",omitempty"`
+ Containers int `json:",omitempty"`
+ ReadOnly bool `json:",omitempty"`
+ Dangling bool `json:",omitempty"`
+
+ // Podman extensions
+ Digest digest.Digest `json:",omitempty"`
+ ConfigDigest digest.Digest `json:",omitempty"`
+}
+
+func (i *ImageSummary) Id() string {
+ return i.ID
+}
+
+func (i *ImageSummary) IsReadOnly() bool {
+ return i.ReadOnly
+}
+
+func (i *ImageSummary) IsDangling() bool {
+ return i.Dangling
+}
+
+type ImageOptions struct {
+ All bool
+ Digests bool
+ Filter []string
+ Format string
+ Noheading bool
+ NoTrunc bool
+ Quiet bool
+ Sort string
+ History bool
+}
+
+type ImageDeleteOptions struct {
+ Force bool
+}
+
+// ImageDeleteResponse is the response for removing an image from storage and containers
+// what was untagged vs actually removed
+type ImageDeleteReport struct {
+ Untagged []string `json:"untagged"`
+ Deleted string `json:"deleted"`
+}
+
+type ImageHistoryOptions struct{}
+
+type ImageHistoryLayer struct {
+ ID string `json:"Id"`
+ Created int64 `json:"Created,omitempty"`
+ CreatedBy string `json:",omitempty"`
+ Tags []string `json:",omitempty"`
+ Size int64 `json:",omitempty"`
+ Comment string `json:",omitempty"`
+}
+
+type ImageHistoryReport struct {
+ Layers []ImageHistoryLayer
+}
+
+type ImageInspectOptions struct {
+ TypeObject string `json:",omitempty"`
+ Format string `json:",omitempty"`
+ Size bool `json:",omitempty"`
+ Latest bool `json:",omitempty"`
+}
+
+type ImageListOptions struct {
+ All bool `json:"all" schema:"all"`
+ Digests bool `json:"digests" schema:"digests"`
+ Filter []string `json:",omitempty"`
+ Filters url.Values `json:"filters" schema:"filters"`
+ Format string `json:",omitempty"`
+ History bool `json:",omitempty"`
+ Noheading bool `json:",omitempty"`
+ NoTrunc bool `json:",omitempty"`
+ Quiet bool `json:",omitempty"`
+ Sort string `json:",omitempty"`
+}
+
+type ImageListReport struct {
+ Images []ImageSummary
+}
+
+type ImagePruneOptions struct {
+ All bool
+ Filter ImageFilter
+}
+
+type ImagePruneReport struct {
+ Report Report
+ Size int64
+}
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
new file mode 100644
index 000000000..6f947dc4d
--- /dev/null
+++ b/pkg/domain/entities/types.go
@@ -0,0 +1,25 @@
+package entities
+
+type Container struct {
+ IdOrNamed
+}
+
+type Volume struct {
+ Identifier
+}
+
+type Report struct {
+ Id []string
+ Err map[string]error
+}
+
+type ContainerDeleteOptions struct{}
+type ContainerDeleteReport struct{ Report }
+type ContainerPruneReport struct{ Report }
+
+type PodDeleteReport struct{ Report }
+type PodPruneOptions struct{}
+type PodPruneReport struct{ Report }
+type VolumeDeleteOptions struct{}
+type VolumeDeleteReport struct{ Report }
+type VolumePruneReport struct{ Report }
diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go
new file mode 100644
index 000000000..ad12d0d01
--- /dev/null
+++ b/pkg/domain/entities/volumes.go
@@ -0,0 +1,41 @@
+package entities
+
+import "time"
+
+// swagger:model VolumeCreate
+type VolumeCreateOptions struct {
+ // New volume's name. Can be left blank
+ Name string `schema:"name"`
+ // Volume driver to use
+ Driver string `schema:"driver"`
+ // User-defined key/value metadata.
+ Label map[string]string `schema:"label"`
+ // Mapping of driver options and values.
+ Options map[string]string `schema:"opts"`
+}
+
+type IdOrNameResponse struct {
+ // The Id or Name of an object
+ IdOrName string
+}
+
+type VolumeConfigResponse struct {
+ // Name of the volume.
+ Name string `json:"name"`
+ Labels map[string]string `json:"labels"`
+ // The volume driver. Empty string or local does not activate a volume
+ // driver, all other volumes will.
+ Driver string `json:"volumeDriver"`
+ // The location the volume is mounted at.
+ MountPoint string `json:"mountPoint"`
+ // Time the volume was created.
+ CreatedTime time.Time `json:"createdAt,omitempty"`
+ // Options to pass to the volume driver. For the local driver, this is
+ // a list of mount options. For other drivers, they are passed to the
+ // volume driver handling the volume.
+ Options map[string]string `json:"volumeOptions,omitempty"`
+ // UID the volume will be created as.
+ UID int `json:"uid"`
+ // GID the volume will be created as.
+ GID int `json:"gid"`
+}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
new file mode 100644
index 000000000..cdcd77246
--- /dev/null
+++ b/pkg/domain/infra/abi/containers.go
@@ -0,0 +1,66 @@
+// +build ABISupport
+
+package abi
+
+import (
+ "context"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/adapter/shortcuts"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+// TODO: Should return *entities.ContainerExistsReport, error
+func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
+ _, err := ic.Libpod.LookupContainer(nameOrId)
+ if err != nil && errors.Cause(err) != define.ErrNoSuchCtr {
+ return nil, err
+ }
+ return &entities.BoolReport{Value: err == nil}, nil
+}
+
+func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, options entities.WaitOptions) ([]entities.WaitReport, error) {
+ var (
+ responses []entities.WaitReport
+ )
+ ctrs, err := shortcuts.GetContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range ctrs {
+ response := entities.WaitReport{Id: c.ID()}
+ exitCode, err := c.WaitForConditionWithInterval(options.Interval, options.Condition)
+ if err != nil {
+ response.Error = err
+ } else {
+ response.ExitCode = exitCode
+ }
+ responses = append(responses, response)
+ }
+ return responses, nil
+}
+
+func (ic *ContainerEngine) ContainerDelete(ctx context.Context, opts entities.ContainerDeleteOptions) (*entities.ContainerDeleteReport, error) {
+ panic("implement me")
+}
+
+func (ic *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) {
+ panic("implement me")
+}
+
+func (ic *ContainerEngine) PodDelete(ctx context.Context, opts entities.PodPruneOptions) (*entities.PodDeleteReport, error) {
+ panic("implement me")
+}
+
+func (ic *ContainerEngine) PodPrune(ctx context.Context) (*entities.PodPruneReport, error) {
+ panic("implement me")
+}
+
+func (ic *ContainerEngine) VolumeDelete(ctx context.Context, opts entities.VolumeDeleteOptions) (*entities.VolumeDeleteReport, error) {
+ panic("implement me")
+}
+
+func (ic *ContainerEngine) VolumePrune(ctx context.Context) (*entities.VolumePruneReport, error) {
+ panic("implement me")
+}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
new file mode 100644
index 000000000..2db08f259
--- /dev/null
+++ b/pkg/domain/infra/abi/images.go
@@ -0,0 +1,131 @@
+// +build ABISupport
+
+package abi
+
+import (
+ "context"
+
+ libpodImage "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/utils"
+)
+
+func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
+ image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
+ if err != nil {
+ return nil, err
+ }
+
+ results, err := ir.Libpod.RemoveImage(ctx, image, opts.Force)
+ if err != nil {
+ return nil, err
+ }
+
+ report := entities.ImageDeleteReport{}
+ if err := utils.DeepCopy(&report, results); err != nil {
+ return nil, err
+ }
+ return &report, nil
+}
+
+func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
+ results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{})
+ if err != nil {
+ return nil, err
+ }
+
+ report := entities.ImagePruneReport{}
+ copy(report.Report.Id, results)
+ return &report, nil
+}
+
+func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) (*entities.ImageListReport, error) {
+ var (
+ images []*libpodImage.Image
+ err error
+ )
+
+ filters := utils.ToLibpodFilters(opts.Filters)
+ if len(filters) > 0 {
+ images, err = ir.Libpod.ImageRuntime().GetImagesWithFilters(filters)
+ } else {
+ images, err = ir.Libpod.ImageRuntime().GetImages()
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ report := entities.ImageListReport{
+ Images: make([]entities.ImageSummary, len(images)),
+ }
+ for i, img := range images {
+ hold := entities.ImageSummary{}
+ if err := utils.DeepCopy(&hold, img); err != nil {
+ return nil, err
+ }
+ report.Images[i] = hold
+ }
+ return &report, nil
+}
+
+func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) {
+ image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
+ if err != nil {
+ return nil, err
+ }
+ results, err := image.History(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ history := entities.ImageHistoryReport{
+ Layers: make([]entities.ImageHistoryLayer, len(results)),
+ }
+
+ for i, layer := range results {
+ history.Layers[i] = ToDomainHistoryLayer(layer)
+ }
+ return &history, nil
+}
+
+func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer {
+ l := entities.ImageHistoryLayer{}
+ l.ID = layer.ID
+ l.Created = layer.Created.Unix()
+ l.CreatedBy = layer.CreatedBy
+ copy(l.Tags, layer.Tags)
+ l.Size = layer.Size
+ l.Comment = layer.Comment
+ return l
+}
+
+// func (r *imageRuntime) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
+// image, err := r.libpod.ImageEngine().NewFromLocal(nameOrId)
+// if err != nil {
+// return nil, err
+// }
+//
+// results, err := r.libpod.RemoveImage(ctx, image, opts.Force)
+// if err != nil {
+// return nil, err
+// }
+//
+// report := entities.ImageDeleteReport{}
+// if err := utils.DeepCopy(&report, results); err != nil {
+// return nil, err
+// }
+// return &report, nil
+// }
+//
+// func (r *imageRuntime) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
+// // TODO: map FilterOptions
+// id, err := r.libpod.ImageEngine().PruneImages(ctx, opts.All, []string{})
+// if err != nil {
+// return nil, err
+// }
+//
+// // TODO: Determine Size
+// report := entities.ImagePruneReport{}
+// copy(report.Report.Id, id)
+// return &report, nil
+// }
diff --git a/pkg/domain/infra/abi/images_test.go b/pkg/domain/infra/abi/images_test.go
new file mode 100644
index 000000000..20ef1b150
--- /dev/null
+++ b/pkg/domain/infra/abi/images_test.go
@@ -0,0 +1,37 @@
+package abi
+
+//
+// import (
+// "context"
+// "testing"
+//
+// "github.com/stretchr/testify/mock"
+// )
+//
+// type MockImageRuntime struct {
+// mock.Mock
+// }
+//
+// func (m *MockImageRuntime) Delete(ctx context.Context, renderer func() interface{}, name string) error {
+// _ = m.Called(ctx, renderer, name)
+// return nil
+// }
+//
+// func TestImageSuccess(t *testing.T) {
+// actual := func() interface{} { return nil }
+//
+// m := new(MockImageRuntime)
+// m.On(
+// "Delete",
+// mock.AnythingOfType("*context.emptyCtx"),
+// mock.AnythingOfType("func() interface {}"),
+// "fedora").
+// Return(nil)
+//
+// r := DirectImageRuntime{m}
+// err := r.Delete(context.TODO(), actual, "fedora")
+// if err != nil {
+// t.Errorf("error should be nil, got: %v", err)
+// }
+// m.AssertExpectations(t)
+// }
diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go
new file mode 100644
index 000000000..6c0e1ee55
--- /dev/null
+++ b/pkg/domain/infra/abi/parse/parse.go
@@ -0,0 +1,68 @@
+package parse
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// Handle volume options from CLI.
+// Parse "o" option to find UID, GID.
+func ParseVolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) {
+ libpodOptions := []libpod.VolumeCreateOption{}
+ volumeOptions := make(map[string]string)
+
+ for key, value := range opts {
+ switch key {
+ case "o":
+ // o has special handling to parse out UID, GID.
+ // These are separate Libpod options.
+ splitVal := strings.Split(value, ",")
+ finalVal := []string{}
+ for _, o := range splitVal {
+ // Options will be formatted as either "opt" or
+ // "opt=value"
+ splitO := strings.SplitN(o, "=", 2)
+ switch strings.ToLower(splitO[0]) {
+ case "uid":
+ if len(splitO) != 2 {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID")
+ }
+ intUID, err := strconv.Atoi(splitO[1])
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert UID %s to integer", splitO[1])
+ }
+ logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID)
+ libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID))
+ case "gid":
+ if len(splitO) != 2 {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "gid option must provide a GID")
+ }
+ intGID, err := strconv.Atoi(splitO[1])
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert GID %s to integer", splitO[1])
+ }
+ logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID)
+ libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID))
+ default:
+ finalVal = append(finalVal, o)
+ }
+ }
+ if len(finalVal) > 0 {
+ volumeOptions[key] = strings.Join(finalVal, ",")
+ }
+ default:
+ volumeOptions[key] = value
+ }
+ }
+
+ if len(volumeOptions) > 0 {
+ libpodOptions = append(libpodOptions, libpod.WithVolumeOptions(volumeOptions))
+ }
+
+ return libpodOptions, nil
+}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
new file mode 100644
index 000000000..de22de68e
--- /dev/null
+++ b/pkg/domain/infra/abi/pods.go
@@ -0,0 +1,19 @@
+// +build ABISupport
+
+package abi
+
+import (
+ "context"
+ "github.com/pkg/errors"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
+ _, err := ic.Libpod.LookupPod(nameOrId)
+ if err != nil && errors.Cause(err) != define.ErrNoSuchPod {
+ return nil, err
+ }
+ return &entities.BoolReport{Value: err == nil}, nil
+}
diff --git a/pkg/domain/infra/abi/runtime.go b/pkg/domain/infra/abi/runtime.go
new file mode 100644
index 000000000..b53fb6d3a
--- /dev/null
+++ b/pkg/domain/infra/abi/runtime.go
@@ -0,0 +1,17 @@
+// +build ABISupport
+
+package abi
+
+import (
+ "github.com/containers/libpod/libpod"
+)
+
+// Image-related runtime linked against libpod library
+type ImageEngine struct {
+ Libpod *libpod.Runtime
+}
+
+// Container-related runtime linked against libpod library
+type ContainerEngine struct {
+ Libpod *libpod.Runtime
+}
diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go
new file mode 100644
index 000000000..0783af441
--- /dev/null
+++ b/pkg/domain/infra/abi/volumes.go
@@ -0,0 +1,38 @@
+// +build ABISupport
+
+package abi
+
+import (
+ "context"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi/parse"
+)
+
+func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.VolumeCreateOptions) (*entities.IdOrNameResponse, error) {
+ var (
+ volumeOptions []libpod.VolumeCreateOption
+ )
+ if len(opts.Name) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeName(opts.Name))
+ }
+ if len(opts.Driver) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(opts.Driver))
+ }
+ if len(opts.Label) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(opts.Label))
+ }
+ if len(opts.Options) > 0 {
+ parsedOptions, err := parse.ParseVolumeOptions(opts.Options)
+ if err != nil {
+ return nil, err
+ }
+ volumeOptions = append(volumeOptions, parsedOptions...)
+ }
+ vol, err := ic.Libpod.NewVolume(ctx, volumeOptions...)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.IdOrNameResponse{IdOrName: vol.Name()}, nil
+}
diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go
new file mode 100644
index 000000000..31f832423
--- /dev/null
+++ b/pkg/domain/infra/runtime_abi.go
@@ -0,0 +1,38 @@
+// +build ABISupport
+
+package infra
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/tunnel"
+)
+
+// NewContainerEngine factory provides a libpod runtime for container-related operations
+func NewContainerEngine(opts entities.EngineOptions) (entities.ContainerEngine, error) {
+ switch opts.EngineMode {
+ case entities.ABIMode:
+ r, err := NewLibpodRuntime(opts.FlagSet, opts.Flags)
+ return r, err
+ case entities.TunnelMode:
+ ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...)
+ return &tunnel.ContainerEngine{ClientCxt: ctx}, err
+ }
+ return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode)
+}
+
+// NewContainerEngine factory provides a libpod runtime for image-related operations
+func NewImageEngine(opts entities.EngineOptions) (entities.ImageEngine, error) {
+ switch opts.EngineMode {
+ case entities.ABIMode:
+ r, err := NewLibpodImageRuntime(opts.FlagSet, opts.Flags)
+ return r, err
+ case entities.TunnelMode:
+ ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...)
+ return &tunnel.ImageEngine{ClientCxt: ctx}, err
+ }
+ return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode)
+}
diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go
new file mode 100644
index 000000000..d2e66c08c
--- /dev/null
+++ b/pkg/domain/infra/runtime_image_proxy.go
@@ -0,0 +1,21 @@
+// +build ABISupport
+
+package infra
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/spf13/pflag"
+)
+
+// ContainerEngine Image Proxy will be EOL'ed after podmanV2 is separated from libpod repo
+
+func NewLibpodImageRuntime(flags *pflag.FlagSet, opts entities.EngineFlags) (entities.ImageEngine, error) {
+ r, err := GetRuntime(context.Background(), flags, opts)
+ if err != nil {
+ return nil, err
+ }
+ return &abi.ImageEngine{Libpod: r}, nil
+}
diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go
new file mode 100644
index 000000000..b835152bf
--- /dev/null
+++ b/pkg/domain/infra/runtime_libpod.go
@@ -0,0 +1,331 @@
+package infra
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/namespaces"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/idtools"
+ "github.com/pkg/errors"
+ flag "github.com/spf13/pflag"
+)
+
+type engineOpts struct {
+ name string
+ renumber bool
+ migrate bool
+ noStore bool
+ withFDS bool
+ flags entities.EngineFlags
+}
+
+// GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers
+func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags, newRuntime string) (*libpod.Runtime, error) {
+ return getRuntime(ctx, fs, &engineOpts{
+ name: newRuntime,
+ renumber: false,
+ migrate: true,
+ noStore: false,
+ withFDS: true,
+ flags: ef,
+ })
+}
+
+// GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify
+func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) {
+ return getRuntime(ctx, fs, &engineOpts{
+ renumber: false,
+ migrate: false,
+ noStore: false,
+ withFDS: false,
+ flags: ef,
+ })
+}
+
+// GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber
+func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) {
+ return getRuntime(ctx, fs, &engineOpts{
+ renumber: true,
+ migrate: false,
+ noStore: false,
+ withFDS: true,
+ flags: ef,
+ })
+}
+
+// GetRuntime generates a new libpod runtime configured by command line options
+func GetRuntime(ctx context.Context, flags *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) {
+ return getRuntime(ctx, flags, &engineOpts{
+ renumber: false,
+ migrate: false,
+ noStore: false,
+ withFDS: true,
+ flags: ef,
+ })
+}
+
+// GetRuntimeNoStore generates a new libpod runtime configured by command line options
+func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) {
+ return getRuntime(ctx, fs, &engineOpts{
+ renumber: false,
+ migrate: false,
+ noStore: true,
+ withFDS: true,
+ flags: ef,
+ })
+}
+
+func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpod.Runtime, error) {
+ options := []libpod.RuntimeOption{}
+ storageOpts := storage.StoreOptions{}
+ storageSet := false
+
+ uidmapFlag := fs.Lookup("uidmap")
+ gidmapFlag := fs.Lookup("gidmap")
+ subuidname := fs.Lookup("subuidname")
+ subgidname := fs.Lookup("subgidname")
+ if (uidmapFlag != nil && gidmapFlag != nil && subuidname != nil && subgidname != nil) &&
+ (uidmapFlag.Changed || gidmapFlag.Changed || subuidname.Changed || subgidname.Changed) {
+ userns, _ := fs.GetString("userns")
+ uidmapVal, _ := fs.GetStringSlice("uidmap")
+ gidmapVal, _ := fs.GetStringSlice("gidmap")
+ subuidVal, _ := fs.GetString("subuidname")
+ subgidVal, _ := fs.GetString("subgidname")
+ mappings, err := ParseIDMapping(namespaces.UsernsMode(userns), uidmapVal, gidmapVal, subuidVal, subgidVal)
+ if err != nil {
+ return nil, err
+ }
+ storageOpts.UIDMap = mappings.UIDMap
+ storageOpts.GIDMap = mappings.GIDMap
+
+ storageSet = true
+ }
+
+ if fs.Changed("root") {
+ storageSet = true
+ storageOpts.GraphRoot = opts.flags.Root
+ }
+ if fs.Changed("runroot") {
+ storageSet = true
+ storageOpts.RunRoot = opts.flags.Runroot
+ }
+ if len(storageOpts.RunRoot) > 50 {
+ return nil, errors.New("the specified runroot is longer than 50 characters")
+ }
+ if fs.Changed("storage-driver") {
+ storageSet = true
+ storageOpts.GraphDriverName = opts.flags.StorageDriver
+ // Overriding the default storage driver caused GraphDriverOptions from storage.conf to be ignored
+ storageOpts.GraphDriverOptions = []string{}
+ }
+ // This should always be checked after storage-driver is checked
+ if len(opts.flags.StorageOpts) > 0 {
+ storageSet = true
+ storageOpts.GraphDriverOptions = opts.flags.StorageOpts
+ }
+ if opts.migrate {
+ options = append(options, libpod.WithMigrate())
+ if opts.name != "" {
+ options = append(options, libpod.WithMigrateRuntime(opts.name))
+ }
+ }
+
+ if opts.renumber {
+ options = append(options, libpod.WithRenumber())
+ }
+
+ // Only set this if the user changes storage config on the command line
+ if storageSet {
+ options = append(options, libpod.WithStorageConfig(storageOpts))
+ }
+
+ if !storageSet && opts.noStore {
+ options = append(options, libpod.WithNoStore())
+ }
+ // TODO CLI flags for image config?
+ // TODO CLI flag for signature policy?
+
+ if len(opts.flags.Namespace) > 0 {
+ options = append(options, libpod.WithNamespace(opts.flags.Namespace))
+ }
+
+ if fs.Changed("runtime") {
+ options = append(options, libpod.WithOCIRuntime(opts.flags.Runtime))
+ }
+
+ if fs.Changed("conmon") {
+ options = append(options, libpod.WithConmonPath(opts.flags.ConmonPath))
+ }
+ if fs.Changed("tmpdir") {
+ options = append(options, libpod.WithTmpDir(opts.flags.TmpDir))
+ }
+ if fs.Changed("network-cmd-path") {
+ options = append(options, libpod.WithNetworkCmdPath(opts.flags.NetworkCmdPath))
+ }
+
+ if fs.Changed("events-backend") {
+ options = append(options, libpod.WithEventsLogger(opts.flags.EventsBackend))
+ }
+
+ if fs.Changed("cgroup-manager") {
+ options = append(options, libpod.WithCgroupManager(opts.flags.CGroupManager))
+ } else {
+ unified, err := cgroups.IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ if rootless.IsRootless() && !unified {
+ options = append(options, libpod.WithCgroupManager("cgroupfs"))
+ }
+ }
+
+ // TODO flag to set libpod static dir?
+ // TODO flag to set libpod tmp dir?
+
+ if fs.Changed("cni-config-dir") {
+ options = append(options, libpod.WithCNIConfigDir(opts.flags.CniConfigDir))
+ }
+ if fs.Changed("default-mounts-file") {
+ options = append(options, libpod.WithDefaultMountsFile(opts.flags.DefaultMountsFile))
+ }
+ if fs.Changed("hooks-dir") {
+ options = append(options, libpod.WithHooksDir(opts.flags.HooksDir...))
+ }
+
+ // TODO flag to set CNI plugins dir?
+
+ // TODO I don't think these belong here?
+ // Will follow up with a different PR to address
+ //
+ // Pod create options
+
+ infraImageFlag := fs.Lookup("infra-image")
+ if infraImageFlag != nil && infraImageFlag.Changed {
+ infraImage, _ := fs.GetString("infra-image")
+ options = append(options, libpod.WithDefaultInfraImage(infraImage))
+ }
+
+ infraCommandFlag := fs.Lookup("infra-command")
+ if infraCommandFlag != nil && infraImageFlag.Changed {
+ infraCommand, _ := fs.GetString("infra-command")
+ options = append(options, libpod.WithDefaultInfraCommand(infraCommand))
+ }
+
+ if !opts.withFDS {
+ options = append(options, libpod.WithEnableSDNotify())
+ }
+ if fs.Changed("config") {
+ return libpod.NewRuntimeFromConfig(ctx, opts.flags.Config, options...)
+ }
+ return libpod.NewRuntime(ctx, options...)
+}
+
+// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
+func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) {
+ options := storage.IDMappingOptions{
+ HostUIDMapping: true,
+ HostGIDMapping: true,
+ }
+
+ if mode.IsKeepID() {
+ if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 {
+ return nil, errors.New("cannot specify custom mappings with --userns=keep-id")
+ }
+ if len(subUIDMap) > 0 || len(subGIDMap) > 0 {
+ return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id")
+ }
+ if rootless.IsRootless() {
+ min := func(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+ }
+
+ uid := rootless.GetRootlessUID()
+ gid := rootless.GetRootlessGID()
+
+ uids, gids, err := rootless.GetConfiguredMappings()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot read mappings")
+ }
+ maxUID, maxGID := 0, 0
+ for _, u := range uids {
+ maxUID += u.Size
+ }
+ for _, g := range gids {
+ maxGID += g.Size
+ }
+
+ options.UIDMap, options.GIDMap = nil, nil
+
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)})
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
+ if maxUID > uid {
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
+ }
+
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)})
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
+ if maxGID > gid {
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
+ }
+
+ options.HostUIDMapping = false
+ options.HostGIDMapping = false
+ }
+ // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op
+ return &options, nil
+ }
+
+ if subGIDMap == "" && subUIDMap != "" {
+ subGIDMap = subUIDMap
+ }
+ if subUIDMap == "" && subGIDMap != "" {
+ subUIDMap = subGIDMap
+ }
+ if len(gidMapSlice) == 0 && len(uidMapSlice) != 0 {
+ gidMapSlice = uidMapSlice
+ }
+ if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 {
+ uidMapSlice = gidMapSlice
+ }
+ if len(uidMapSlice) == 0 && subUIDMap == "" && os.Getuid() != 0 {
+ uidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getuid())}
+ }
+ if len(gidMapSlice) == 0 && subGIDMap == "" && os.Getuid() != 0 {
+ gidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getgid())}
+ }
+
+ if subUIDMap != "" && subGIDMap != "" {
+ mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap)
+ if err != nil {
+ return nil, err
+ }
+ options.UIDMap = mappings.UIDs()
+ options.GIDMap = mappings.GIDs()
+ }
+ parsedUIDMap, err := idtools.ParseIDMap(uidMapSlice, "UID")
+ if err != nil {
+ return nil, err
+ }
+ parsedGIDMap, err := idtools.ParseIDMap(gidMapSlice, "GID")
+ if err != nil {
+ return nil, err
+ }
+ options.UIDMap = append(options.UIDMap, parsedUIDMap...)
+ options.GIDMap = append(options.GIDMap, parsedGIDMap...)
+ if len(options.UIDMap) > 0 {
+ options.HostUIDMapping = false
+ }
+ if len(options.GIDMap) > 0 {
+ options.HostGIDMapping = false
+ }
+ return &options, nil
+}
diff --git a/pkg/domain/infra/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go
new file mode 100644
index 000000000..4095ae6e2
--- /dev/null
+++ b/pkg/domain/infra/runtime_proxy.go
@@ -0,0 +1,21 @@
+// +build ABISupport
+
+package infra
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ flag "github.com/spf13/pflag"
+)
+
+// ContainerEngine Proxy will be EOL'ed after podmanV2 is separated from libpod repo
+
+func NewLibpodRuntime(flags *flag.FlagSet, opts entities.EngineFlags) (entities.ContainerEngine, error) {
+ r, err := GetRuntime(context.Background(), flags, opts)
+ if err != nil {
+ return nil, err
+ }
+ return &abi.ContainerEngine{Libpod: r}, nil
+}
diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go
new file mode 100644
index 000000000..5816ef0c0
--- /dev/null
+++ b/pkg/domain/infra/runtime_tunnel.go
@@ -0,0 +1,35 @@
+// +build !ABISupport
+
+package infra
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/tunnel"
+)
+
+func NewContainerEngine(opts entities.EngineOptions) (entities.ContainerEngine, error) {
+ switch opts.EngineMode {
+ case entities.ABIMode:
+ return nil, fmt.Errorf("direct runtime not supported")
+ case entities.TunnelMode:
+ ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...)
+ return &tunnel.ContainerEngine{ClientCxt: ctx}, err
+ }
+ return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode)
+}
+
+// NewImageEngine factory provides a libpod runtime for image-related operations
+func NewImageEngine(opts entities.EngineOptions) (entities.ImageEngine, error) {
+ switch opts.EngineMode {
+ case entities.ABIMode:
+ return nil, fmt.Errorf("direct image runtime not supported")
+ case entities.TunnelMode:
+ ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...)
+ return &tunnel.ImageEngine{ClientCxt: ctx}, err
+ }
+ return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode)
+}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
new file mode 100644
index 000000000..8bf74126d
--- /dev/null
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -0,0 +1,42 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
+ exists, err := containers.Exists(ic.ClientCxt, nameOrId)
+ return &entities.BoolReport{Value: exists}, err
+}
+
+func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, options entities.WaitOptions) ([]entities.WaitReport, error) {
+ var (
+ responses []entities.WaitReport
+ )
+ cons, err := getContainersByContext(ic.ClientCxt, false, namesOrIds)
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range cons {
+ response := entities.WaitReport{Id: c.ID}
+ exitCode, err := containers.Wait(ic.ClientCxt, c.ID, &options.Condition)
+ if err != nil {
+ response.Error = err
+ } else {
+ response.ExitCode = exitCode
+ }
+ responses = append(responses, response)
+ }
+ return responses, nil
+}
+
+func (r *ContainerEngine) ContainerDelete(ctx context.Context, opts entities.ContainerDeleteOptions) (*entities.ContainerDeleteReport, error) {
+ panic("implement me")
+}
+
+func (r *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) {
+ panic("implement me")
+}
diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go
new file mode 100644
index 000000000..d5a3224c2
--- /dev/null
+++ b/pkg/domain/infra/tunnel/helpers.go
@@ -0,0 +1,41 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/pkg/errors"
+)
+
+func getContainersByContext(contextWithConnection context.Context, all bool, namesOrIds []string) ([]libpod.ListContainer, error) {
+ var (
+ cons []libpod.ListContainer
+ )
+ if all && len(namesOrIds) > 0 {
+ return nil, errors.New("cannot lookup containers and all")
+ }
+ c, err := containers.List(contextWithConnection, nil, &bindings.PTrue, nil, nil, nil, &bindings.PTrue)
+ if err != nil {
+ return nil, err
+ }
+ if all {
+ return c, err
+ }
+ for _, id := range namesOrIds {
+ var found bool
+ for _, con := range c {
+ if id == con.ID || util.StringInSlice(id, con.Names) {
+ cons = append(cons, con)
+ found = true
+ break
+ }
+ }
+ if !found {
+ return nil, errors.Errorf("unable to find container %q", id)
+ }
+ }
+ return cons, nil
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
new file mode 100644
index 000000000..718685e57
--- /dev/null
+++ b/pkg/domain/infra/tunnel/images.go
@@ -0,0 +1,81 @@
+package tunnel
+
+import (
+ "context"
+ "net/url"
+
+ images "github.com/containers/libpod/pkg/bindings/images"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/utils"
+)
+
+func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
+ results, err := images.Remove(ir.ClientCxt, nameOrId, &opts.Force)
+ if err != nil {
+ return nil, err
+ }
+
+ report := entities.ImageDeleteReport{
+ Untagged: nil,
+ Deleted: "",
+ }
+
+ for _, e := range results {
+ if a, ok := e["Deleted"]; ok {
+ report.Deleted = a
+ }
+
+ if a, ok := e["Untagged"]; ok {
+ report.Untagged = append(report.Untagged, a)
+ }
+ }
+ return &report, err
+}
+
+func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) (*entities.ImageListReport, error) {
+ images, err := images.List(ir.ClientCxt, &opts.All, opts.Filters)
+ if err != nil {
+ return nil, err
+ }
+
+ report := entities.ImageListReport{
+ Images: make([]entities.ImageSummary, len(images)),
+ }
+ for i, img := range images {
+ hold := entities.ImageSummary{}
+ if err := utils.DeepCopy(&hold, img); err != nil {
+ return nil, err
+ }
+ report.Images[i] = hold
+ }
+ return &report, nil
+}
+
+func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) {
+ results, err := images.History(ir.ClientCxt, nameOrId)
+ if err != nil {
+ return nil, err
+ }
+
+ history := entities.ImageHistoryReport{
+ Layers: make([]entities.ImageHistoryLayer, len(results)),
+ }
+
+ for i, layer := range results {
+ hold := entities.ImageHistoryLayer{}
+ _ = utils.DeepCopy(&hold, layer)
+ history.Layers[i] = hold
+ }
+ return &history, nil
+}
+
+func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
+ results, err := images.Prune(ir.ClientCxt, url.Values{})
+ if err != nil {
+ return nil, err
+ }
+
+ report := entities.ImagePruneReport{}
+ copy(report.Report.Id, results)
+ return &report, nil
+}
diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go
new file mode 100644
index 000000000..500069d51
--- /dev/null
+++ b/pkg/domain/infra/tunnel/pods.go
@@ -0,0 +1,13 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/bindings/pods"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
+ exists, err := pods.Exists(ic.ClientCxt, nameOrId)
+ return &entities.BoolReport{Value: exists}, err
+}
diff --git a/pkg/domain/infra/tunnel/runtime.go b/pkg/domain/infra/tunnel/runtime.go
new file mode 100644
index 000000000..eb9b34e4a
--- /dev/null
+++ b/pkg/domain/infra/tunnel/runtime.go
@@ -0,0 +1,33 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+// Image-related runtime using an ssh-tunnel to utilize Podman service
+type ImageEngine struct {
+ ClientCxt context.Context
+}
+
+// Container-related runtime using an ssh-tunnel to utilize Podman service
+type ContainerEngine struct {
+ ClientCxt context.Context
+}
+
+func (r *ContainerEngine) PodDelete(ctx context.Context, opts entities.PodPruneOptions) (*entities.PodDeleteReport, error) {
+ panic("implement me")
+}
+
+func (r *ContainerEngine) PodPrune(ctx context.Context) (*entities.PodPruneReport, error) {
+ panic("implement me")
+}
+
+func (r *ContainerEngine) VolumeDelete(ctx context.Context, opts entities.VolumeDeleteOptions) (*entities.VolumeDeleteReport, error) {
+ panic("implement me")
+}
+
+func (r *ContainerEngine) VolumePrune(ctx context.Context) (*entities.VolumePruneReport, error) {
+ panic("implement me")
+}
diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go
new file mode 100644
index 000000000..49cf6a2f6
--- /dev/null
+++ b/pkg/domain/infra/tunnel/volumes.go
@@ -0,0 +1,16 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/bindings/volumes"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.VolumeCreateOptions) (*entities.IdOrNameResponse, error) {
+ response, err := volumes.Create(ic.ClientCxt, opts)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.IdOrNameResponse{IdOrName: response.Name}, nil
+}
diff --git a/pkg/domain/utils/utils.go b/pkg/domain/utils/utils.go
new file mode 100644
index 000000000..c17769f62
--- /dev/null
+++ b/pkg/domain/utils/utils.go
@@ -0,0 +1,41 @@
+package utils
+
+import (
+ "net/url"
+ "strings"
+
+ jsoniter "github.com/json-iterator/go"
+)
+
+var json = jsoniter.ConfigCompatibleWithStandardLibrary
+
+// DeepCopy does a deep copy of a structure
+// Error checking of parameters delegated to json engine
+var DeepCopy = func(dst interface{}, src interface{}) error {
+ payload, err := json.Marshal(src)
+ if err != nil {
+ return err
+ }
+
+ err = json.Unmarshal(payload, dst)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func ToLibpodFilters(f url.Values) (filters []string) {
+ for k, v := range f {
+ filters = append(filters, k+"="+v[0])
+ }
+ return
+}
+
+func ToUrlValues(f []string) (filters url.Values) {
+ filters = make(url.Values)
+ for _, v := range f {
+ t := strings.SplitN(v, "=", 2)
+ filters.Add(t[0], t[1])
+ }
+ return
+}
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index db898e706..6643bfbbf 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -108,10 +108,9 @@ do_pause ()
}
static char **
-get_cmd_line_args (pid_t pid)
+get_cmd_line_args ()
{
int fd;
- char path[PATH_MAX];
char *buffer;
size_t allocated;
size_t used = 0;
@@ -119,11 +118,7 @@ get_cmd_line_args (pid_t pid)
int i, argc = 0;
char **argv;
- if (pid)
- sprintf (path, "/proc/%d/cmdline", pid);
- else
- strcpy (path, "/proc/self/cmdline");
- fd = open (path, O_RDONLY);
+ fd = open ("/proc/self/cmdline", O_RDONLY);
if (fd < 0)
return NULL;
@@ -196,7 +191,7 @@ can_use_shortcut ()
return false;
#endif
- argv = get_cmd_line_args (0);
+ argv = get_cmd_line_args ();
if (argv == NULL)
return false;
@@ -542,7 +537,6 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
int
reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
{
- pid_t ppid = getpid ();
char uid[16];
char gid[16];
char **argv;
@@ -559,7 +553,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
sprintf (uid, "%d", geteuid ());
sprintf (gid, "%d", getegid ());
- argv = get_cmd_line_args (ppid);
+ argv = get_cmd_line_args ();
if (argv == NULL)
{
fprintf (stderr, "cannot read argv: %s\n", strerror (errno));
@@ -724,7 +718,6 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re
int ret;
pid_t pid;
char b;
- pid_t ppid = getpid ();
char **argv;
char uid[16];
char gid[16];
@@ -801,7 +794,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re
_exit (EXIT_FAILURE);
}
- argv = get_cmd_line_args (ppid);
+ argv = get_cmd_line_args ();
if (argv == NULL)
{
fprintf (stderr, "cannot read argv: %s\n", strerror (errno));
diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go
index febfc2268..6ecd3cf98 100644
--- a/pkg/rootlessport/rootlessport_linux.go
+++ b/pkg/rootlessport/rootlessport_linux.go
@@ -19,6 +19,7 @@ import (
"io/ioutil"
"os"
"os/exec"
+ "os/signal"
"syscall"
"github.com/containernetworking/plugins/pkg/ns"
@@ -101,6 +102,28 @@ func parent() error {
return err
}
+ sigC := make(chan os.Signal, 1)
+ signal.Notify(sigC, syscall.SIGPIPE)
+ defer func() {
+ // dummy signal to terminate the goroutine
+ sigC <- syscall.SIGKILL
+ }()
+ go func() {
+ defer func() {
+ signal.Stop(sigC)
+ close(sigC)
+ }()
+
+ s := <-sigC
+ if s == syscall.SIGPIPE {
+ if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil {
+ syscall.Dup2(int(f.Fd()), 1) // nolint:errcheck
+ syscall.Dup2(int(f.Fd()), 2) // nolint:errcheck
+ f.Close()
+ }
+ }
+ }()
+
// create the parent driver
stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport")
if err != nil {
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 9b2255d61..12dfed8c3 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -144,6 +144,7 @@ type CreateConfig struct {
InitPath string //init-path
Image string
ImageID string
+ RawImageName string
BuiltinImgVolumes map[string]struct{} // volumes defined in the image config
ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore
Interactive bool //interactive
@@ -348,7 +349,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
options = append(options, nsOpts...)
// Gather up the options for NewContainer which consist of With... funcs
- options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image))
+ options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, c.RawImageName))
options = append(options, libpod.WithConmonPidFile(c.ConmonPidFile))
options = append(options, libpod.WithLabels(c.Labels))
options = append(options, libpod.WithShmSize(c.Resources.ShmSize))
diff --git a/pkg/specgen/config_unsupported.go b/pkg/specgen/config_unsupported.go
index 5d24ac39c..c2d3257c9 100644
--- a/pkg/specgen/config_unsupported.go
+++ b/pkg/specgen/config_unsupported.go
@@ -3,10 +3,11 @@
package specgen
import (
+ "github.com/containers/libpod/libpod/image"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
-func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
+func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*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
index 99a99083b..aefbe7405 100644
--- a/pkg/specgen/create.go
+++ b/pkg/specgen/create.go
@@ -36,7 +36,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
return nil, err
}
- options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image))
+ options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
runtimeSpec, err := s.toOCISpec(rt, newImage)
if err != nil {
diff --git a/pkg/specgen/pod.go b/pkg/specgen/pod.go
new file mode 100644
index 000000000..1aada83c4
--- /dev/null
+++ b/pkg/specgen/pod.go
@@ -0,0 +1,140 @@
+package specgen
+
+import (
+ "net"
+
+ "github.com/cri-o/ocicni/pkg/ocicni"
+)
+
+// PodBasicConfig contains basic configuration options for pods.
+type PodBasicConfig struct {
+ // Name is the name of the pod.
+ // If not provided, a name will be generated when the pod is created.
+ // Optional.
+ Name string `json:"name,omitempty"`
+ // Hostname is the pod's hostname. If not set, the name of the pod will
+ // be used (if a name was not provided here, the name auto-generated for
+ // the pod will be used). This will be used by the infra container and
+ // all containers in the pod as long as the UTS namespace is shared.
+ // Optional.
+ Hostname string `json:"hostname,omitempty"`
+ // Labels are key-value pairs that are used to add metadata to pods.
+ // Optional.
+ Labels map[string]string `json:"labels,omitempty"`
+ // NoInfra tells the pod not to create an infra container. If this is
+ // done, many networking-related options will become unavailable.
+ // Conflicts with setting any options in PodNetworkConfig, and the
+ // InfraCommand and InfraImages in this struct.
+ // Optional.
+ NoInfra bool `json:"no_infra,omitempty"`
+ // InfraCommand sets the command that will be used to start the infra
+ // container.
+ // If not set, the default set in the Libpod configuration file will be
+ // used.
+ // Conflicts with NoInfra=true.
+ // Optional.
+ InfraCommand []string `json:"infra_command,omitempty"`
+ // InfraImage is the image that will be used for the infra container.
+ // If not set, the default set in the Libpod configuration file will be
+ // used.
+ // Conflicts with NoInfra=true.
+ // Optional.
+ InfraImage string `json:"infra_image,omitempty"`
+ // SharedNamespaces instructs the pod to share a set of namespaces.
+ // Shared namespaces will be joined (by default) by every container
+ // which joins the pod.
+ // If not set and NoInfra is false, the pod will set a default set of
+ // namespaces to share.
+ // Conflicts with NoInfra=true.
+ // Optional.
+ SharedNamespaces []string `json:"shared_namespaces,omitempty"`
+}
+
+// PodNetworkConfig contains networking configuration for a pod.
+type PodNetworkConfig struct {
+ // NetNS is the configuration to use for the infra container's network
+ // namespace. This network will, by default, be shared with all
+ // containers in the pod.
+ // Cannot be set to FromContainer and FromPod.
+ // Setting this to anything except "" conflicts with NoInfra=true.
+ // Defaults to Bridge as root and Slirp as rootless.
+ // Mandatory.
+ NetNS Namespace `json:"netns,omitempty"`
+ // StaticIP sets a static IP for the infra container. As the infra
+ // container's network is used for the entire pod by default, this will
+ // thus be a static IP for the whole pod.
+ // Only available if NetNS is set to Bridge (the default for root).
+ // As such, conflicts with NoInfra=true by proxy.
+ // Optional.
+ StaticIP *net.IP `json:"static_ip,omitempty"`
+ // StaticMAC sets a static MAC for the infra container. As the infra
+ // container's network is used for the entire pod by default, this will
+ // thus be a static MAC for the entire pod.
+ // Only available if NetNS is set to Bridge (the default for root).
+ // As such, conflicts with NoInfra=true by proxy.
+ // Optional.
+ StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"`
+ // PortMappings is a set of ports to map into the infra container.
+ // As, by default, containers share their network with the infra
+ // container, this will forward the ports to the entire pod.
+ // Only available if NetNS is set to Bridge or Slirp.
+ // Optional.
+ PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"`
+ // CNINetworks is a list of CNI networks that the infra container will
+ // join. As, by default, containers share their network with the infra
+ // container, these networks will effectively be joined by the
+ // entire pod.
+ // Only available when NetNS is set to Bridge, the default for root.
+ // Optional.
+ CNINetworks []string `json:"cni_networks,omitempty"`
+ // NoManageResolvConf indicates that /etc/resolv.conf should not be
+ // managed by the pod. Instead, each container will create and manage a
+ // separate resolv.conf as if they had not joined a pod.
+ // Conflicts with NoInfra=true and DNSServer, DNSSearch, DNSOption.
+ // Optional.
+ NoManageResolvConf bool `json:"no_manage_resolv_conf,omitempty"`
+ // DNSServer is a set of DNS servers that will be used in the infra
+ // container's resolv.conf, which will, by default, be shared with all
+ // containers in the pod.
+ // If not provided, the host's DNS servers will be used, unless the only
+ // server set is a localhost address. As the container cannot connect to
+ // the host's localhost, a default server will instead be set.
+ // Conflicts with NoInfra=true.
+ // Optional.
+ DNSServer []net.IP `json:"dns_server,omitempty"`
+ // DNSSearch is a set of DNS search domains that will be used in the
+ // infra container's resolv.conf, which will, by default, be shared with
+ // all containers in the pod.
+ // If not provided, DNS search domains from the host's resolv.conf will
+ // be used.
+ // Conflicts with NoInfra=true.
+ // Optional.
+ DNSSearch []string `json:"dns_search,omitempty"`
+ // DNSOption is a set of DNS options that will be used in the infra
+ // container's resolv.conf, which will, by default, be shared with all
+ // containers in the pod.
+ // Conflicts with NoInfra=true.
+ // Optional.
+ DNSOption []string `json:"dns_option,omitempty"`
+ // NoManageHosts indicates that /etc/hosts should not be managed by the
+ // pod. Instead, each container will create a separate /etc/hosts as
+ // they would if not in a pod.
+ // Conflicts with HostAdd.
+ NoManageHosts bool `json:"no_manage_hosts,omitempty"`
+ // HostAdd is a set of hosts that will be added to the infra container's
+ // /etc/hosts that will, by default, be shared with all containers in
+ // the pod.
+ // Conflicts with NoInfra=true and NoManageHosts.
+ // Optional.
+ HostAdd []string `json:"hostadd,omitempty"`
+}
+
+// PodCgroupConfig contains configuration options about a pod's cgroups.
+// This will be expanded in future updates to pods.
+type PodCgroupConfig struct {
+ // CgroupParent is the parent for the CGroup that the pod will create.
+ // This pod cgroup will, in turn, be the default cgroup parent for all
+ // containers in the pod.
+ // Optional.
+ CgroupParent string `json:"cgroup_parent,omitempty"`
+}
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index e1dfe4dc5..b123c1da5 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -53,7 +53,7 @@ type ContainerBasicConfig struct {
Terminal bool `json:"terminal,omitempty"`
// Stdin is whether the container will keep its STDIN open.
Stdin bool `json:"stdin,omitempty"`
- // Labels are key-valid labels that are used to add metadata to
+ // Labels are key-value pairs that are used to add metadata to
// containers.
// Optional.
Labels map[string]string `json:"labels,omitempty"`
@@ -143,6 +143,10 @@ type ContainerStorageConfig struct {
// Conflicts with Rootfs.
// At least one of Image or Rootfs must be specified.
Image string `json:"image"`
+ // RawImageName is the unprocessed and not-normalized user-specified image
+ // name. One use case for having this data at hand are auto-updates where
+ // the _exact_ user input is needed in order to look-up the correct image.
+ RawImageName string `json:"raw_image_name,omitempty"`
// Rootfs is the path to a directory that will be used as the
// container's root filesystem. No modification will be made to the
// directory, it will be directly mounted into the container as root.
diff --git a/pkg/systemd/dbus.go b/pkg/systemd/dbus.go
new file mode 100644
index 000000000..df24667a1
--- /dev/null
+++ b/pkg/systemd/dbus.go
@@ -0,0 +1,47 @@
+package systemd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/coreos/go-systemd/v22/dbus"
+ godbus "github.com/godbus/dbus/v5"
+)
+
+func dbusAuthRootlessConnection(createBus func(opts ...godbus.ConnOption) (*godbus.Conn, error)) (*godbus.Conn, error) {
+ conn, err := createBus()
+ if err != nil {
+ return nil, err
+ }
+
+ methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(rootless.GetRootlessUID()))}
+
+ err = conn.Auth(methods)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+
+ return conn, nil
+}
+
+func newRootlessConnection() (*dbus.Conn, error) {
+ return dbus.NewConnection(func() (*godbus.Conn, error) {
+ return dbusAuthRootlessConnection(func(opts ...godbus.ConnOption) (*godbus.Conn, error) {
+ path := filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "systemd/private")
+ return godbus.Dial(fmt.Sprintf("unix:path=%s", path))
+ })
+ })
+}
+
+// ConnectToDBUS returns a DBUS connection. It works both as root and non-root
+// users.
+func ConnectToDBUS() (*dbus.Conn, error) {
+ if rootless.IsRootless() {
+ return newRootlessConnection()
+ }
+ return dbus.NewSystemdConnection()
+}
diff --git a/pkg/systemd/generate/systemdgen.go b/pkg/systemd/generate/systemdgen.go
index 4cd7745c0..eb15d4927 100644
--- a/pkg/systemd/generate/systemdgen.go
+++ b/pkg/systemd/generate/systemdgen.go
@@ -16,6 +16,10 @@ import (
"github.com/sirupsen/logrus"
)
+// EnvVariable "PODMAN_SYSTEMD_UNIT" is set in all generated systemd units and
+// is set to the unit's (unique) name.
+const EnvVariable = "PODMAN_SYSTEMD_UNIT"
+
// ContainerInfo contains data required for generating a container's systemd
// unit file.
type ContainerInfo struct {
@@ -57,6 +61,8 @@ type ContainerInfo struct {
// RunCommand is a post-processed variant of CreateCommand and used for
// the ExecStart field in generic unit files.
RunCommand string
+ // EnvVariable is generate.EnvVariable and must not be set.
+ EnvVariable string
}
var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"}
@@ -94,6 +100,7 @@ Before={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{
{{- end}}
[Service]
+Environment={{.EnvVariable}}=%n
Restart={{.RestartPolicy}}
{{- if .New}}
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
@@ -138,6 +145,8 @@ func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, erro
info.Executable = executable
}
+ info.EnvVariable = EnvVariable
+
// Assemble the ExecStart command when creating a new container.
//
// Note that we cannot catch all corner cases here such that users
diff --git a/pkg/systemd/generate/systemdgen_test.go b/pkg/systemd/generate/systemdgen_test.go
index bbdccdcf8..3269405a6 100644
--- a/pkg/systemd/generate/systemdgen_test.go
+++ b/pkg/systemd/generate/systemdgen_test.go
@@ -44,6 +44,7 @@ Wants=network.target
After=network-online.target
[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401
@@ -64,6 +65,7 @@ Wants=network.target
After=network-online.target
[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStart=/usr/bin/podman start foobar
ExecStop=/usr/bin/podman stop -t 10 foobar
@@ -88,6 +90,7 @@ BindsTo=a.service b.service c.service pod.service
After=a.service b.service c.service pod.service
[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStart=/usr/bin/podman start foobar
ExecStop=/usr/bin/podman stop -t 10 foobar
@@ -110,6 +113,7 @@ Requires=container-1.service container-2.service
Before=container-1.service container-2.service
[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStart=/usr/bin/podman start jadda-jadda-infra
ExecStop=/usr/bin/podman stop -t 10 jadda-jadda-infra
@@ -130,6 +134,7 @@ Wants=network.target
After=network-online.target
[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
@@ -152,6 +157,7 @@ Wants=network.target
After=network-online.target
[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
@@ -174,6 +180,7 @@ Wants=network.target
After=network-online.target
[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon -d awesome-image:latest
diff --git a/pkg/util/utils_linux_test.go b/pkg/util/utils_linux_test.go
new file mode 100644
index 000000000..38e6dbef9
--- /dev/null
+++ b/pkg/util/utils_linux_test.go
@@ -0,0 +1,29 @@
+package util
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGetImageConfigStopSignal(t *testing.T) {
+ // Linux-only beause parsing signal names is not supported on non-Linux systems by
+ // pkg/signal.
+ stopSignalValidInt, err := GetImageConfig([]string{"STOPSIGNAL 9"})
+ require.Nil(t, err)
+ assert.Equal(t, stopSignalValidInt.StopSignal, "9")
+
+ stopSignalValidString, err := GetImageConfig([]string{"STOPSIGNAL SIGKILL"})
+ require.Nil(t, err)
+ assert.Equal(t, stopSignalValidString.StopSignal, "9")
+
+ _, err = GetImageConfig([]string{"STOPSIGNAL 0"})
+ assert.NotNil(t, err)
+
+ _, err = GetImageConfig([]string{"STOPSIGNAL garbage"})
+ assert.NotNil(t, err)
+
+ _, err = GetImageConfig([]string{"STOPSIGNAL "})
+ assert.NotNil(t, err)
+}
diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go
index f4b03599d..0995d1e20 100644
--- a/pkg/util/utils_test.go
+++ b/pkg/util/utils_test.go
@@ -219,25 +219,6 @@ func TestGetImageConfigLabel(t *testing.T) {
assert.NotNil(t, err)
}
-func TestGetImageConfigStopSignal(t *testing.T) {
- stopSignalValidInt, err := GetImageConfig([]string{"STOPSIGNAL 9"})
- require.Nil(t, err)
- assert.Equal(t, stopSignalValidInt.StopSignal, "9")
-
- stopSignalValidString, err := GetImageConfig([]string{"STOPSIGNAL SIGKILL"})
- require.Nil(t, err)
- assert.Equal(t, stopSignalValidString.StopSignal, "9")
-
- _, err = GetImageConfig([]string{"STOPSIGNAL 0"})
- assert.NotNil(t, err)
-
- _, err = GetImageConfig([]string{"STOPSIGNAL garbage"})
- assert.NotNil(t, err)
-
- _, err = GetImageConfig([]string{"STOPSIGNAL "})
- assert.NotNil(t, err)
-}
-
func TestGetImageConfigOnBuild(t *testing.T) {
onBuildOne, err := GetImageConfig([]string{"ONBUILD ADD /testdir1"})
require.Nil(t, err)
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 94726bbbd..55427771c 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -846,11 +846,6 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO
workDir = *opts.Workdir
}
- var detachKeys string
- if opts.DetachKeys != nil {
- detachKeys = *opts.DetachKeys
- }
-
resizeChan := make(chan remotecommand.TerminalSize)
reader, writer, _, pipeWriter, streams := setupStreams(call)
@@ -870,8 +865,17 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO
}
}()
+ execConfig := new(libpod.ExecConfig)
+ execConfig.Command = opts.Cmd
+ execConfig.Terminal = opts.Tty
+ execConfig.Privileged = opts.Privileged
+ execConfig.Environment = envs
+ execConfig.User = user
+ execConfig.WorkDir = workDir
+ execConfig.DetachKeys = opts.DetachKeys
+
go func() {
- ec, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, resizeChan, detachKeys)
+ ec, err := ctr.Exec(execConfig, streams, resizeChan)
if err != nil {
logrus.Errorf(err.Error())
}
diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go
index 50aaaaa44..e88d010c5 100644
--- a/pkg/varlinkapi/system.go
+++ b/pkg/varlinkapi/system.go
@@ -10,7 +10,7 @@ import (
"time"
"github.com/containers/image/v5/pkg/sysregistriesv2"
- "github.com/containers/libpod/cmd/podman/varlink"
+ iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod/define"
"github.com/sirupsen/logrus"
)