summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers.go27
-rw-r--r--pkg/api/handlers/compat/containers_archive.go12
-rw-r--r--pkg/api/handlers/compat/containers_attach.go51
-rw-r--r--pkg/api/handlers/compat/containers_start.go9
-rw-r--r--pkg/api/handlers/compat/containers_stats.go6
-rw-r--r--pkg/api/handlers/compat/containers_stop.go (renamed from pkg/api/handlers/compat/container_start.go)0
-rw-r--r--pkg/api/handlers/compat/events.go12
-rw-r--r--pkg/api/handlers/compat/exec.go72
-rw-r--r--pkg/api/handlers/compat/images.go30
-rw-r--r--pkg/api/handlers/compat/images_build.go1
-rw-r--r--pkg/api/handlers/compat/images_push.go15
-rw-r--r--pkg/api/handlers/compat/networks.go301
-rw-r--r--pkg/api/handlers/compat/ping.go8
-rw-r--r--pkg/api/handlers/compat/resize.go86
-rw-r--r--pkg/api/handlers/compat/swagger.go28
-rw-r--r--pkg/api/handlers/compat/types.go2
-rw-r--r--pkg/api/handlers/compat/version.go43
-rw-r--r--pkg/api/handlers/handler.go6
-rw-r--r--pkg/api/handlers/libpod/containers.go4
-rw-r--r--pkg/api/handlers/libpod/containers_create.go3
-rw-r--r--pkg/api/handlers/libpod/copy.go12
-rw-r--r--pkg/api/handlers/libpod/generate.go38
-rw-r--r--pkg/api/handlers/libpod/healthcheck.go24
-rw-r--r--pkg/api/handlers/libpod/images.go128
-rw-r--r--pkg/api/handlers/libpod/manifests.go5
-rw-r--r--pkg/api/handlers/libpod/networks.go68
-rw-r--r--pkg/api/handlers/libpod/play.go82
-rw-r--r--pkg/api/handlers/libpod/swagger.go28
-rw-r--r--pkg/api/handlers/libpod/system.go13
-rw-r--r--pkg/api/handlers/swagger/swagger.go7
-rw-r--r--pkg/api/handlers/types.go13
-rw-r--r--pkg/api/handlers/utils/errors.go8
-rw-r--r--pkg/api/handlers/utils/handler.go86
-rw-r--r--pkg/api/handlers/utils/handler_test.go139
-rw-r--r--pkg/api/handlers/utils/images.go33
-rw-r--r--pkg/api/server/register_archive.go171
-rw-r--r--pkg/api/server/register_containers.go6
-rw-r--r--pkg/api/server/register_events.go5
-rw-r--r--pkg/api/server/register_exec.go24
-rw-r--r--pkg/api/server/register_generate.go41
-rw-r--r--pkg/api/server/register_images.go290
-rw-r--r--pkg/api/server/register_networks.go190
-rw-r--r--pkg/api/server/register_play.go42
-rw-r--r--pkg/api/server/register_system.go17
-rw-r--r--pkg/api/server/register_version.go21
-rw-r--r--pkg/api/server/server.go12
-rw-r--r--pkg/api/server/swagger.go43
-rw-r--r--pkg/api/tags.yaml8
-rw-r--r--pkg/auth/auth.go216
-rw-r--r--pkg/autoupdate/autoupdate.go34
-rw-r--r--pkg/bindings/bindings.go13
-rw-r--r--pkg/bindings/connection.go105
-rw-r--r--pkg/bindings/containers/checkpoint.go4
-rw-r--r--pkg/bindings/containers/commit.go2
-rw-r--r--pkg/bindings/containers/containers.go289
-rw-r--r--pkg/bindings/containers/create.go2
-rw-r--r--pkg/bindings/containers/diff.go2
-rw-r--r--pkg/bindings/containers/exec.go4
-rw-r--r--pkg/bindings/containers/healthcheck.go2
-rw-r--r--pkg/bindings/containers/logs.go78
-rw-r--r--pkg/bindings/containers/mount.go6
-rw-r--r--pkg/bindings/generate/generate.go32
-rw-r--r--pkg/bindings/images/diff.go2
-rw-r--r--pkg/bindings/images/images.go232
-rw-r--r--pkg/bindings/images/rm.go65
-rw-r--r--pkg/bindings/manifests/manifests.go36
-rw-r--r--pkg/bindings/network/network.go66
-rw-r--r--pkg/bindings/play/play.go49
-rw-r--r--pkg/bindings/pods/pods.go28
-rw-r--r--pkg/bindings/system/info.go2
-rw-r--r--pkg/bindings/system/system.go86
-rw-r--r--pkg/bindings/test/attach_test.go110
-rw-r--r--pkg/bindings/test/auth_test.go143
-rw-r--r--pkg/bindings/test/common_test.go2
-rw-r--r--pkg/bindings/test/containers_test.go213
-rw-r--r--pkg/bindings/test/exec_test.go4
-rw-r--r--pkg/bindings/test/images_test.go48
-rw-r--r--pkg/bindings/test/manifests_test.go24
-rw-r--r--pkg/bindings/test/pods_test.go16
-rw-r--r--pkg/bindings/test/system_test.go25
-rw-r--r--pkg/bindings/test/test_suite_test.go5
-rw-r--r--pkg/bindings/test/volumes_test.go2
-rw-r--r--pkg/bindings/version.go3
-rw-r--r--pkg/bindings/volumes/volumes.go10
-rw-r--r--pkg/cgroups/cgroups.go4
-rw-r--r--pkg/domain/entities/auto-update.go13
-rw-r--r--pkg/domain/entities/containers.go54
-rw-r--r--pkg/domain/entities/engine.go36
-rw-r--r--pkg/domain/entities/engine_container.go16
-rw-r--r--pkg/domain/entities/engine_image.go8
-rw-r--r--pkg/domain/entities/engine_system.go14
-rw-r--r--pkg/domain/entities/generate.go20
-rw-r--r--pkg/domain/entities/images.go74
-rw-r--r--pkg/domain/entities/manifest.go22
-rw-r--r--pkg/domain/entities/network.go55
-rw-r--r--pkg/domain/entities/play.go37
-rw-r--r--pkg/domain/entities/pods.go2
-rw-r--r--pkg/domain/entities/system.go77
-rw-r--r--pkg/domain/entities/types.go3
-rw-r--r--pkg/domain/infra/abi/auto-update.go17
-rw-r--r--pkg/domain/infra/abi/containers.go198
-rw-r--r--pkg/domain/infra/abi/containers_runlabel.go280
-rw-r--r--pkg/domain/infra/abi/events.go5
-rw-r--r--pkg/domain/infra/abi/generate.go91
-rw-r--r--pkg/domain/infra/abi/healthcheck.go7
-rw-r--r--pkg/domain/infra/abi/images.go291
-rw-r--r--pkg/domain/infra/abi/images_list.go29
-rw-r--r--pkg/domain/infra/abi/manifest.go127
-rw-r--r--pkg/domain/infra/abi/network.go292
-rw-r--r--pkg/domain/infra/abi/play.go554
-rw-r--r--pkg/domain/infra/abi/pods.go4
-rw-r--r--pkg/domain/infra/abi/pods_stats.go3
-rw-r--r--pkg/domain/infra/abi/runtime.go5
-rw-r--r--pkg/domain/infra/abi/system.go280
-rw-r--r--pkg/domain/infra/abi/system_novalink.go14
-rw-r--r--pkg/domain/infra/abi/system_varlink.go49
-rw-r--r--pkg/domain/infra/abi/terminal/terminal_linux.go14
-rw-r--r--pkg/domain/infra/abi/trust.go171
-rw-r--r--pkg/domain/infra/abi/volumes.go6
-rw-r--r--pkg/domain/infra/runtime_abi.go31
-rw-r--r--pkg/domain/infra/runtime_abi_unsupported.go14
-rw-r--r--pkg/domain/infra/runtime_image_proxy.go21
-rw-r--r--pkg/domain/infra/runtime_libpod.go3
-rw-r--r--pkg/domain/infra/runtime_proxy.go8
-rw-r--r--pkg/domain/infra/tunnel/auto-update.go12
-rw-r--r--pkg/domain/infra/tunnel/containers.go175
-rw-r--r--pkg/domain/infra/tunnel/events.go3
-rw-r--r--pkg/domain/infra/tunnel/generate.go5
-rw-r--r--pkg/domain/infra/tunnel/helpers.go4
-rw-r--r--pkg/domain/infra/tunnel/images.go40
-rw-r--r--pkg/domain/infra/tunnel/manifest.go23
-rw-r--r--pkg/domain/infra/tunnel/network.go40
-rw-r--r--pkg/domain/infra/tunnel/play.go12
-rw-r--r--pkg/domain/infra/tunnel/runtime.go5
-rw-r--r--pkg/domain/infra/tunnel/system.go17
-rw-r--r--pkg/domain/infra/tunnel/trust.go16
-rw-r--r--pkg/errorhandling/errorhandling.go36
-rw-r--r--pkg/network/devices.go7
-rw-r--r--pkg/network/files.go31
-rw-r--r--pkg/network/netconflist.go9
-rw-r--r--pkg/network/network.go35
-rw-r--r--pkg/ps/ps.go2
-rw-r--r--pkg/rootless/rootless_linux.c48
-rw-r--r--pkg/rootlessport/rootlessport_linux.go26
-rw-r--r--pkg/signal/signal_common.go41
-rw-r--r--pkg/signal/signal_linux.go36
-rw-r--r--pkg/signal/signal_unsupported.go89
-rw-r--r--pkg/spec/namespaces.go8
-rw-r--r--pkg/spec/security.go7
-rw-r--r--pkg/spec/spec.go55
-rw-r--r--pkg/specgen/generate/config_linux.go8
-rw-r--r--pkg/specgen/generate/config_linux_nocgo.go3
-rw-r--r--pkg/specgen/generate/container.go42
-rw-r--r--pkg/specgen/generate/container_create.go54
-rw-r--r--pkg/specgen/generate/namespaces.go77
-rw-r--r--pkg/specgen/generate/oci.go72
-rw-r--r--pkg/specgen/generate/pod_create.go15
-rw-r--r--pkg/specgen/generate/ports.go333
-rw-r--r--pkg/specgen/generate/security.go92
-rw-r--r--pkg/specgen/namespaces.go44
-rw-r--r--pkg/specgen/pod_validate.go18
-rw-r--r--pkg/specgen/podspecgen.go6
-rw-r--r--pkg/specgen/specgen.go57
-rw-r--r--pkg/trust/config.go12
-rw-r--r--pkg/util/mountOpts.go1
-rw-r--r--pkg/util/mountOpts_linux.go2
-rw-r--r--pkg/varlinkapi/containers.go10
-rw-r--r--pkg/varlinkapi/pods.go8
-rw-r--r--pkg/varlinkapi/remote_client.go6
-rw-r--r--pkg/varlinkapi/system.go2
-rw-r--r--pkg/varlinkapi/transfers.go2
171 files changed, 7655 insertions, 1170 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 239e41af4..cea4bd0f6 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -4,6 +4,7 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
+ "io"
"net/http"
"strconv"
"strings"
@@ -295,7 +296,9 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}()
w.WriteHeader(http.StatusOK)
- var builder strings.Builder
+
+ var frame strings.Builder
+ header := make([]byte, 8)
for ok := true; ok; ok = query.Follow {
for line := range logChannel {
if _, found := r.URL.Query()["until"]; found {
@@ -304,10 +307,8 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
}
- // Reset variables we're ready to loop again
- builder.Reset()
- header := [8]byte{}
-
+ // Reset buffer we're ready to loop again
+ frame.Reset()
switch line.Device {
case "stdout":
if !query.Stdout {
@@ -327,17 +328,17 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
if query.Timestamps {
- builder.WriteString(line.Time.Format(time.RFC3339))
- builder.WriteRune(' ')
+ frame.WriteString(line.Time.Format(time.RFC3339))
+ frame.WriteString(" ")
}
- 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 {
+ frame.WriteString(line.Msg)
+
+ binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
+ if _, err := w.Write(header[0:8]); err != nil {
log.Errorf("unable to write log output header: %q", err)
}
- if _, err := fmt.Fprint(w, builder.String()); err != nil {
- log.Errorf("unable to write builder string: %q", err)
+ if _, err := io.WriteString(w, frame.String()); err != nil {
+ log.Errorf("unable to write frame string: %q", err)
}
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go
new file mode 100644
index 000000000..c3a26873e
--- /dev/null
+++ b/pkg/api/handlers/compat/containers_archive.go
@@ -0,0 +1,12 @@
+package compat
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
+
+func Archive(w http.ResponseWriter, r *http.Request) {
+ utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented"))
+}
diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go
index 80ad52aee..990140ee1 100644
--- a/pkg/api/handlers/compat/containers_attach.go
+++ b/pkg/api/handlers/compat/containers_attach.go
@@ -10,9 +10,14 @@ import (
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
- "k8s.io/client-go/tools/remotecommand"
)
+// AttachHeader is the literal header sent for upgraded/hijacked connections for
+// attach, sourced from Docker at:
+// https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
+// Using literally to ensure compatibility with existing clients.
+const AttachHeader = "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n"
+
func AttachContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@@ -85,11 +90,11 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
// For Docker compatibility, we need to re-initialize containers in these states.
if state == define.ContainerStateConfigured || state == define.ContainerStateExited {
if err := ctr.Init(r.Context()); err != nil {
- utils.InternalServerError(w, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
+ utils.Error(w, "Container in wrong state", http.StatusConflict, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
return
}
} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
- utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers"))
+ utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers - currently in state %s", state.String()))
return
}
@@ -106,10 +111,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
return
}
- // This header string sourced from Docker:
- // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
- // Using literally to ensure compatability with existing clients.
- fmt.Fprintf(connection, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
+ fmt.Fprintf(connection, AttachHeader)
logrus.Debugf("Hijack for attach of container %s successful", ctr.ID())
@@ -124,38 +126,3 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) {
logrus.Debugf("Attach for container %s completed successfully", ctr.ID())
}
-
-func ResizeContainer(w http.ResponseWriter, r *http.Request) {
- runtime := r.Context().Value("runtime").(*libpod.Runtime)
- decoder := r.Context().Value("decoder").(*schema.Decoder)
-
- query := struct {
- Height uint16 `schema:"h"`
- Width uint16 `schema:"w"`
- }{}
- if err := decoder.Decode(&query, r.URL.Query()); err != nil {
- // This is not a 400, despite the fact that is should be, for
- // compatibility reasons.
- utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options"))
- return
- }
-
- name := utils.GetName(r)
- ctr, err := runtime.LookupContainer(name)
- if err != nil {
- utils.ContainerNotFound(w, name, err)
- return
- }
-
- newSize := remotecommand.TerminalSize{
- Width: query.Width,
- Height: query.Height,
- }
- if err := ctr.AttachResize(newSize); err != nil {
- utils.InternalServerError(w, err)
- return
- }
- // This is not a 204, even though we write nothing, for compatibility
- // reasons.
- utils.WriteResponse(w, http.StatusOK, "")
-}
diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go
index 67bd287ab..cdbc8ff76 100644
--- a/pkg/api/handlers/compat/containers_start.go
+++ b/pkg/api/handlers/compat/containers_start.go
@@ -3,11 +3,12 @@ package compat
import (
"net/http"
+ "github.com/sirupsen/logrus"
+
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/gorilla/schema"
- "github.com/pkg/errors"
)
func StartContainer(w http.ResponseWriter, r *http.Request) {
@@ -23,8 +24,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
}
if len(query.DetachKeys) > 0 {
// TODO - start does not support adding detach keys
- utils.BadRequest(w, "detachKeys", query.DetachKeys, errors.New("the detachKeys parameter is not supported yet"))
- return
+ logrus.Info("the detach keys parameter is not supported on start container")
}
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
@@ -33,7 +33,6 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
utils.ContainerNotFound(w, name, err)
return
}
-
state, err := con.State()
if err != nil {
utils.InternalServerError(w, err)
@@ -43,7 +42,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusNotModified, "")
return
}
- if err := con.Start(r.Context(), false); err != nil {
+ if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil {
utils.InternalServerError(w, err)
return
}
diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go
index 53ad0a632..048321add 100644
--- a/pkg/api/handlers/compat/containers_stats.go
+++ b/pkg/api/handlers/compat/containers_stats.go
@@ -45,12 +45,12 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- if state != define.ContainerStateRunning && !query.Stream {
- utils.InternalServerError(w, define.ErrCtrStateInvalid)
+ if state != define.ContainerStateRunning {
+ utils.Error(w, "Container not running and streaming requested", http.StatusConflict, define.ErrCtrStateInvalid)
return
}
- stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{})
+ stats, err := ctnr.GetContainerStats(&define.ContainerStats{})
if err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name))
return
diff --git a/pkg/api/handlers/compat/container_start.go b/pkg/api/handlers/compat/containers_stop.go
index d26ef2c82..d26ef2c82 100644
--- a/pkg/api/handlers/compat/container_start.go
+++ b/pkg/api/handlers/compat/containers_stop.go
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 7ebfb0d1e..577ddd0a1 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -26,7 +26,10 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
Since string `schema:"since"`
Until string `schema:"until"`
Filters map[string][]string `schema:"filters"`
- }{}
+ Stream bool `schema:"stream"`
+ }{
+ Stream: true,
+ }
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
}
@@ -41,9 +44,10 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
if len(query.Since) > 0 || len(query.Until) > 0 {
fromStart = true
}
+
eventChannel := make(chan *events.Event)
go func() {
- readOpts := events.ReadOptions{FromStart: fromStart, Stream: true, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until}
+ readOpts := events.ReadOptions{FromStart: fromStart, Stream: query.Stream, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until}
eventsError = runtime.Events(readOpts)
}()
if eventsError != nil {
@@ -55,7 +59,9 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
// If client disappears we need to stop listening for events
go func(done <-chan struct{}) {
<-done
- close(eventChannel)
+ if _, ok := <-eventChannel; ok {
+ close(eventChannel)
+ }
}(r.Context().Done())
// Headers need to be written out before turning Writer() over to json encoder
diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go
index ec1a8ac96..6865a3319 100644
--- a/pkg/api/handlers/compat/exec.go
+++ b/pkg/api/handlers/compat/exec.go
@@ -104,4 +104,76 @@ func ExecInspectHandler(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, inspectOut)
+
+ // Only for the Compat API: we want to remove sessions that were
+ // stopped. This is very hacky, but should suffice for now.
+ if !utils.IsLibpodRequest(r) && inspectOut.CanRemove {
+ logrus.Infof("Pruning stale exec session %s from container %s", sessionID, sessionCtr.ID())
+ if err := sessionCtr.ExecRemove(sessionID, false); err != nil && errors.Cause(err) != define.ErrNoSuchExecSession {
+ logrus.Errorf("Error removing stale exec session %s from container %s: %v", sessionID, sessionCtr.ID(), err)
+ }
+ }
+}
+
+// ExecStartHandler runs a given exec session.
+func ExecStartHandler(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ sessionID := mux.Vars(r)["id"]
+
+ // TODO: We should read/support Tty and Detach from here.
+ bodyParams := new(handlers.ExecStartConfig)
+
+ if err := json.NewDecoder(r.Body).Decode(&bodyParams); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to decode parameters for %s", r.URL.String()))
+ return
+ }
+ if bodyParams.Detach {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Errorf("Detached exec is not yet supported"))
+ return
+ }
+ // TODO: Verify TTY setting against what inspect session was made with
+
+ sessionCtr, err := runtime.GetExecSessionContainer(sessionID)
+ if err != nil {
+ utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err)
+ return
+ }
+
+ logrus.Debugf("Starting exec session %s of container %s", sessionID, sessionCtr.ID())
+
+ state, err := sessionCtr.State()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if state != define.ContainerStateRunning {
+ utils.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict, errors.Errorf("cannot exec in a container that is not running; container %s is %s", sessionCtr.ID(), state.String()))
+ return
+ }
+
+ // Hijack the connection
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ utils.InternalServerError(w, errors.Errorf("unable to hijack connection"))
+ return
+ }
+
+ connection, buffer, err := hijacker.Hijack()
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection"))
+ return
+ }
+
+ fmt.Fprintf(connection, AttachHeader)
+
+ logrus.Debugf("Hijack for attach of container %s exec session %s successful", sessionCtr.ID(), sessionID)
+
+ if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, connection, buffer, nil, nil, nil); err != nil {
+ logrus.Errorf("Error attaching to container %s exec session %s: %v", sessionCtr.ID(), sessionID, err)
+ }
+
+ logrus.Debugf("Attach for container %s exec session %s completed successfully", sessionCtr.ID(), sessionID)
}
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index ea9cbd691..b64ed0036 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -15,6 +15,7 @@ import (
image2 "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/api/types"
@@ -251,19 +252,32 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
return
}
- /*
- fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.
- repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image.
- tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
- */
fromImage := query.FromImage
if len(query.Tag) >= 1 {
fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag)
}
- // TODO
- // We are eating the output right now because we haven't talked about how to deal with multiple responses yet
- img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing)
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+
+ registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf}
+ if sys := runtime.SystemContext(); sys != nil {
+ registryOpts.DockerCertPath = sys.DockerCertPath
+ }
+ img, err := runtime.ImageRuntime().New(r.Context(),
+ fromImage,
+ "", // signature policy
+ authfile,
+ nil, // writer
+ &registryOpts,
+ image2.SigningOptions{},
+ nil, // label
+ util.PullImageMissing,
+ )
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index e208e6ddc..e9d8fd719 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -226,6 +226,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
id, _, err := runtime.Build(r.Context(), buildOptions, query.Dockerfile)
if err != nil {
utils.InternalServerError(w, err)
+ return
}
// Find image ID that was built...
diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go
index 2260d5557..47976b7c9 100644
--- a/pkg/api/handlers/compat/images_push.go
+++ b/pkg/api/handlers/compat/images_push.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/auth"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@@ -48,13 +49,17 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
- // TODO: the X-Registry-Auth header is not checked yet here nor in any other
- // endpoint. Pushing does NOT work with authentication at the moment.
- dockerRegistryOptions := &image.DockerRegistryOptions{}
- authfile := ""
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+
+ dockerRegistryOptions := &image.DockerRegistryOptions{DockerRegistryCreds: authConf}
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- authfile = sys.AuthFilePath
+ dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
}
err = newImage.PushImageToHeuristicDestination(
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
new file mode 100644
index 000000000..c52ca093f
--- /dev/null
+++ b/pkg/api/handlers/compat/networks.go
@@ -0,0 +1,301 @@
+package compat
+
+import (
+ "encoding/json"
+ "net"
+ "net/http"
+ "os"
+ "syscall"
+ "time"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/containers/libpod/pkg/network"
+ "github.com/docker/docker/api/types"
+ dockerNetwork "github.com/docker/docker/api/types/network"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+type CompatInspectNetwork struct {
+ types.NetworkResource
+}
+
+func InspectNetwork(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ // FYI scope and version are currently unused but are described by the API
+ // Leaving this for if/when we have to enable these
+ //query := struct {
+ // scope string
+ // verbose bool
+ //}{
+ // // override any golang type defaults
+ //}
+ //decoder := r.Context().Value("decoder").(*schema.Decoder)
+ //if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ // utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ // return
+ //}
+ config, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ name := utils.GetName(r)
+ _, err = network.InspectNetwork(config, name)
+ if err != nil {
+ // TODO our network package does not distinguish between not finding a
+ // specific network vs not being able to read it
+ utils.InternalServerError(w, err)
+ return
+ }
+ report, err := getNetworkResourceByName(name, runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.NetworkResource, error) {
+ var (
+ ipamConfigs []dockerNetwork.IPAMConfig
+ )
+ config, err := runtime.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ containerEndpoints := map[string]types.EndpointResource{}
+ // Get the network path so we can get created time
+ networkConfigPath, err := network.GetCNIConfigPathByName(config, name)
+ if err != nil {
+ return nil, err
+ }
+ f, err := os.Stat(networkConfigPath)
+ if err != nil {
+ return nil, err
+ }
+ stat := f.Sys().(*syscall.Stat_t)
+ cons, err := runtime.GetAllContainers()
+ if err != nil {
+ return nil, err
+ }
+ conf, err := libcni.ConfListFromFile(networkConfigPath)
+ if err != nil {
+ return nil, err
+ }
+
+ // No Bridge plugin means we bail
+ bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver)
+ if err != nil {
+ return nil, err
+ }
+ for _, outer := range bridge.IPAM.Ranges {
+ for _, n := range outer {
+ ipamConfig := dockerNetwork.IPAMConfig{
+ Subnet: n.Subnet,
+ Gateway: n.Gateway,
+ }
+ ipamConfigs = append(ipamConfigs, ipamConfig)
+ }
+ }
+
+ for _, con := range cons {
+ data, err := con.Inspect(false)
+ if err != nil {
+ return nil, err
+ }
+ if netData, ok := data.NetworkSettings.Networks[name]; ok {
+ containerEndpoint := types.EndpointResource{
+ Name: netData.NetworkID,
+ EndpointID: netData.EndpointID,
+ MacAddress: netData.MacAddress,
+ IPv4Address: netData.IPAddress,
+ IPv6Address: netData.GlobalIPv6Address,
+ }
+ containerEndpoints[con.ID()] = containerEndpoint
+ }
+ }
+ report := types.NetworkResource{
+ Name: name,
+ ID: "",
+ Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert
+ Scope: "",
+ Driver: network.DefaultNetworkDriver,
+ EnableIPv6: false,
+ IPAM: dockerNetwork.IPAM{
+ Driver: "default",
+ Options: nil,
+ Config: ipamConfigs,
+ },
+ Internal: false,
+ Attachable: false,
+ Ingress: false,
+ ConfigFrom: dockerNetwork.ConfigReference{},
+ ConfigOnly: false,
+ Containers: containerEndpoints,
+ Options: nil,
+ Labels: nil,
+ Peers: nil,
+ Services: nil,
+ }
+ return &report, nil
+}
+
+func genericPluginsToBridge(plugins []*libcni.NetworkConfig, pluginType string) (network.HostLocalBridge, error) {
+ var bridge network.HostLocalBridge
+ generic, err := findPluginByName(plugins, pluginType)
+ if err != nil {
+ return bridge, err
+ }
+ err = json.Unmarshal(generic, &bridge)
+ return bridge, err
+}
+
+func findPluginByName(plugins []*libcni.NetworkConfig, pluginType string) ([]byte, error) {
+ for _, p := range plugins {
+ if pluginType == p.Network.Type {
+ return p.Bytes, nil
+ }
+ }
+ return nil, errors.New("unable to find bridge plugin")
+}
+
+func ListNetworks(w http.ResponseWriter, r *http.Request) {
+ var (
+ reports []*types.NetworkResource
+ )
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Filters map[string][]string `schema:"filters"`
+ }{
+ // override any golang type defaults
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ config, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ // TODO remove when filters are implemented
+ if len(query.Filters) > 0 {
+ utils.InternalServerError(w, errors.New("filters for listing networks is not implemented"))
+ return
+ }
+ netNames, err := network.GetNetworkNamesFromFileSystem(config)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ for _, name := range netNames {
+ report, err := getNetworkResourceByName(name, runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ reports = append(reports, report)
+ }
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
+
+func CreateNetwork(w http.ResponseWriter, r *http.Request) {
+ var (
+ name string
+ networkCreate types.NetworkCreateRequest
+ )
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+
+ if len(networkCreate.Name) > 0 {
+ name = networkCreate.Name
+ }
+ // At present I think we should just suport the bridge driver
+ // and allow demand to make us consider more
+ if networkCreate.Driver != network.DefaultNetworkDriver {
+ utils.InternalServerError(w, errors.New("network create only supports the bridge driver"))
+ return
+ }
+ ncOptions := entities.NetworkCreateOptions{
+ Driver: network.DefaultNetworkDriver,
+ Internal: networkCreate.Internal,
+ }
+ if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
+ if len(networkCreate.IPAM.Config) > 1 {
+ utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config"))
+ return
+ }
+
+ if len(networkCreate.IPAM.Config[0].Subnet) > 0 {
+ _, subnet, err := net.ParseCIDR(networkCreate.IPAM.Config[0].Subnet)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ ncOptions.Subnet = *subnet
+ }
+ if len(networkCreate.IPAM.Config[0].Gateway) > 0 {
+ ncOptions.Gateway = net.ParseIP(networkCreate.IPAM.Config[0].Gateway)
+ }
+ if len(networkCreate.IPAM.Config[0].IPRange) > 0 {
+ _, IPRange, err := net.ParseCIDR(networkCreate.IPAM.Config[0].IPRange)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ ncOptions.Range = *IPRange
+ }
+ }
+ ce := abi.ContainerEngine{Libpod: runtime}
+ _, err := ce.NetworkCreate(r.Context(), name, ncOptions)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ report := types.NetworkCreate{
+ CheckDuplicate: networkCreate.CheckDuplicate,
+ Driver: networkCreate.Driver,
+ Scope: networkCreate.Scope,
+ EnableIPv6: networkCreate.EnableIPv6,
+ IPAM: networkCreate.IPAM,
+ Internal: networkCreate.Internal,
+ Attachable: networkCreate.Attachable,
+ Ingress: networkCreate.Ingress,
+ ConfigOnly: networkCreate.ConfigOnly,
+ ConfigFrom: networkCreate.ConfigFrom,
+ Options: networkCreate.Options,
+ Labels: networkCreate.Labels,
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ config, err := runtime.GetConfig()
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ name := utils.GetName(r)
+ exists, err := network.Exists(config, name)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if !exists {
+ utils.Error(w, "network not found", http.StatusNotFound, err)
+ return
+ }
+ if err := network.RemoveNetwork(config, name); err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusNoContent, "")
+}
diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go
index 6e77e270f..abee3d8e8 100644
--- a/pkg/api/handlers/compat/ping.go
+++ b/pkg/api/handlers/compat/ping.go
@@ -5,22 +5,22 @@ import (
"net/http"
"github.com/containers/buildah"
- "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
)
// Ping returns headers to client about the service
//
// This handler must always be the same for the compatibility and libpod URL trees!
// Clients will use the Header availability to test which backend engine is in use.
+// Note: Additionally handler supports GET and HEAD methods
func Ping(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("API-Version", handlers.DefaultApiVersion)
+ w.Header().Set("API-Version", utils.ApiVersion[utils.CompatTree][utils.CurrentApiVersion].String())
w.Header().Set("BuildKit-Version", "")
w.Header().Set("Docker-Experimental", "true")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Pragma", "no-cache")
- // API-Version and Libpod-API-Version may not always be equal
- w.Header().Set("Libpod-API-Version", handlers.DefaultApiVersion)
+ w.Header().Set("Libpod-API-Version", utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String())
w.Header().Set("Libpod-Buildha-Version", buildah.Version)
w.WriteHeader(http.StatusOK)
diff --git a/pkg/api/handlers/compat/resize.go b/pkg/api/handlers/compat/resize.go
new file mode 100644
index 000000000..231b53175
--- /dev/null
+++ b/pkg/api/handlers/compat/resize.go
@@ -0,0 +1,86 @@
+package compat
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "k8s.io/client-go/tools/remotecommand"
+)
+
+func ResizeTTY(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ // /containers/{id}/resize
+ query := struct {
+ height uint16 `schema:"h"`
+ width uint16 `schema:"w"`
+ }{
+ // override any golang type defaults
+ }
+
+ 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
+ }
+
+ sz := remotecommand.TerminalSize{
+ Width: query.width,
+ Height: query.height,
+ }
+
+ var status int
+ name := utils.GetName(r)
+ switch {
+ case strings.Contains(r.URL.Path, "/containers/"):
+ ctnr, err := runtime.LookupContainer(name)
+ if err != nil {
+ utils.ContainerNotFound(w, name, err)
+ return
+ }
+ if state, err := ctnr.State(); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain container state"))
+ return
+ } else if state != define.ContainerStateRunning {
+ utils.Error(w, "Container not running", http.StatusConflict,
+ fmt.Errorf("container %q in wrong state %q", name, state.String()))
+ return
+ }
+ if err := ctnr.AttachResize(sz); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
+ return
+ }
+ // This is not a 204, even though we write nothing, for compatibility
+ // reasons.
+ status = http.StatusOK
+ case strings.Contains(r.URL.Path, "/exec/"):
+ ctnr, err := runtime.GetExecSessionContainer(name)
+ if err != nil {
+ utils.SessionNotFound(w, name, err)
+ return
+ }
+ if state, err := ctnr.State(); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain session container state"))
+ return
+ } else if state != define.ContainerStateRunning {
+ utils.Error(w, "Container not running", http.StatusConflict,
+ fmt.Errorf("container %q in wrong state %q", name, state.String()))
+ return
+ }
+ if err := ctnr.ExecResize(name, sz); err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session"))
+ return
+ }
+ // This is not a 204, even though we write nothing, for compatibility
+ // reasons.
+ status = http.StatusCreated
+ }
+ utils.WriteResponse(w, status, "")
+}
diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go
index ce83aa32f..dc94a7ebd 100644
--- a/pkg/api/handlers/compat/swagger.go
+++ b/pkg/api/handlers/compat/swagger.go
@@ -3,6 +3,7 @@ package compat
import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/storage/pkg/archive"
+ "github.com/docker/docker/api/types"
)
// Create container
@@ -35,3 +36,30 @@ type swagChangesResponse struct {
Changes []archive.Change
}
}
+
+// Network inspect
+// swagger:response CompatNetworkInspect
+type swagCompatNetworkInspect struct {
+ // in:body
+ Body types.NetworkResource
+}
+
+// Network list
+// swagger:response CompatNetworkList
+type swagCompatNetworkList struct {
+ // in:body
+ Body []types.NetworkResource
+}
+
+// Network create
+// swagger:model NetworkCreateRequest
+type NetworkCreateRequest struct {
+ types.NetworkCreateRequest
+}
+
+// Network create
+// swagger:response CompatNetworkCreate
+type swagCompatNetworkCreateResponse struct {
+ // in:body
+ Body struct{ types.NetworkCreate }
+}
diff --git a/pkg/api/handlers/compat/types.go b/pkg/api/handlers/compat/types.go
index b8d06760f..6d47ede64 100644
--- a/pkg/api/handlers/compat/types.go
+++ b/pkg/api/handlers/compat/types.go
@@ -48,7 +48,7 @@ type StatsJSON struct {
Stats
Name string `json:"name,omitempty"`
- ID string `json:"id,omitempty"`
+ ID string `json:"Id,omitempty"`
// Networks request version >=1.21
Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go
index 35a95b562..bfc226bb8 100644
--- a/pkg/api/handlers/compat/version.go
+++ b/pkg/api/handlers/compat/version.go
@@ -8,8 +8,8 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
docker "github.com/docker/docker/api/types"
"github.com/pkg/errors"
)
@@ -34,34 +34,35 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
Name: "Podman Engine",
Version: versionInfo.Version,
Details: map[string]string{
- "APIVersion": handlers.DefaultApiVersion,
+ "APIVersion": utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String(),
"Arch": goRuntime.GOARCH,
"BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
"Experimental": "true",
"GitCommit": versionInfo.GitCommit,
"GoVersion": versionInfo.GoVersion,
"KernelVersion": infoData.Host.Kernel,
- "MinAPIVersion": handlers.MinimalApiVersion,
+ "MinAPIVersion": utils.ApiVersion[utils.LibpodTree][utils.MinimalApiVersion].String(),
"Os": goRuntime.GOOS,
},
}}
- utils.WriteResponse(w, http.StatusOK, handlers.Version{Version: docker.Version{
- Platform: struct {
- Name string
- }{
- Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
- },
- APIVersion: components[0].Details["APIVersion"],
- Arch: components[0].Details["Arch"],
- BuildTime: components[0].Details["BuildTime"],
- Components: components,
- Experimental: true,
- GitCommit: components[0].Details["GitCommit"],
- GoVersion: components[0].Details["GoVersion"],
- KernelVersion: components[0].Details["KernelVersion"],
- MinAPIVersion: components[0].Details["MinAPIVersion"],
- Os: components[0].Details["Os"],
- Version: components[0].Version,
- }})
+ utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{
+ Version: docker.Version{
+ Platform: struct {
+ Name string
+ }{
+ Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
+ },
+ APIVersion: components[0].Details["APIVersion"],
+ Arch: components[0].Details["Arch"],
+ BuildTime: components[0].Details["BuildTime"],
+ Components: components,
+ Experimental: true,
+ GitCommit: components[0].Details["GitCommit"],
+ GoVersion: components[0].Details["GoVersion"],
+ KernelVersion: components[0].Details["KernelVersion"],
+ MinAPIVersion: components[0].Details["MinAPIVersion"],
+ Os: components[0].Details["Os"],
+ Version: components[0].Version,
+ }})
}
diff --git a/pkg/api/handlers/handler.go b/pkg/api/handlers/handler.go
deleted file mode 100644
index 2dd2c886b..000000000
--- a/pkg/api/handlers/handler.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package handlers
-
-const (
- DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
- MinimalApiVersion = "1.24"
-)
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index 3902bdc9b..50f6b1a38 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -66,6 +66,10 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
+ if len(pss) == 0 {
+ utils.WriteResponse(w, http.StatusOK, "[]")
+ return
+ }
utils.WriteResponse(w, http.StatusOK, pss)
}
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
index 40b6cacdb..71f440bce 100644
--- a/pkg/api/handlers/libpod/containers_create.go
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -5,10 +5,9 @@ import (
"encoding/json"
"net/http"
- "github.com/containers/libpod/pkg/domain/entities"
-
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/specgen/generate"
"github.com/pkg/errors"
diff --git a/pkg/api/handlers/libpod/copy.go b/pkg/api/handlers/libpod/copy.go
new file mode 100644
index 000000000..a3b404bce
--- /dev/null
+++ b/pkg/api/handlers/libpod/copy.go
@@ -0,0 +1,12 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/pkg/errors"
+)
+
+func Archive(w http.ResponseWriter, r *http.Request) {
+ utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented"))
+}
diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go
new file mode 100644
index 000000000..23320d346
--- /dev/null
+++ b/pkg/api/handlers/libpod/generate.go
@@ -0,0 +1,38 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func GenerateKube(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Service bool `schema:"service"`
+ }{
+ // Defaults would go here.
+ }
+
+ 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
+ }
+
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ options := entities.GenerateKubeOptions{Service: query.Service}
+ report, err := containerEngine.GenerateKube(r.Context(), utils.GetName(r), options)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating YAML"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, report.Reader)
+}
diff --git a/pkg/api/handlers/libpod/healthcheck.go b/pkg/api/handlers/libpod/healthcheck.go
index 6eb2ab0e3..0ca3574b7 100644
--- a/pkg/api/handlers/libpod/healthcheck.go
+++ b/pkg/api/handlers/libpod/healthcheck.go
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
)
@@ -12,32 +13,27 @@ func RunHealthCheck(w http.ResponseWriter, r *http.Request) {
name := utils.GetName(r)
status, err := runtime.HealthCheck(name)
if err != nil {
- if status == libpod.HealthCheckContainerNotFound {
+ if status == define.HealthCheckContainerNotFound {
utils.ContainerNotFound(w, name, err)
return
}
- if status == libpod.HealthCheckNotDefined {
+ if status == define.HealthCheckNotDefined {
utils.Error(w, "no healthcheck defined", http.StatusConflict, err)
return
}
- if status == libpod.HealthCheckContainerStopped {
+ if status == define.HealthCheckContainerStopped {
utils.Error(w, "container not running", http.StatusConflict, err)
return
}
utils.InternalServerError(w, err)
return
}
- ctr, err := runtime.LookupContainer(name)
- if err != nil {
- utils.InternalServerError(w, err)
- return
+ hcStatus := define.HealthCheckUnhealthy
+ if status == define.HealthCheckSuccess {
+ hcStatus = define.HealthCheckHealthy
}
-
- hcLog, err := ctr.GetHealthCheckLog()
- if err != nil {
- utils.InternalServerError(w, err)
- return
+ report := define.HealthCheckResults{
+ Status: hcStatus,
}
-
- utils.WriteResponse(w, http.StatusOK, hcLog)
+ utils.WriteResponse(w, http.StatusOK, report)
}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index f7be5ce9a..4b277d39c 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -21,12 +21,15 @@ import (
image2 "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/util"
utils2 "github.com/containers/libpod/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// Commit
@@ -55,13 +58,6 @@ func ImageExists(w http.ResponseWriter, r *http.Request) {
func ImageTree(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
-
- img, err := runtime.ImageRuntime().NewFromLocal(name)
- if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
- return
- }
-
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
WhatRequires bool `schema:"whatrequires"`
@@ -73,14 +69,18 @@ func ImageTree(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
-
- tree, err := img.GenerateTree(query.WhatRequires)
+ ir := abi.ImageEngine{Libpod: runtime}
+ options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires}
+ report, err := ir.Tree(r.Context(), name, options)
if err != nil {
+ if errors.Cause(err) == define.ErrNoSuchImage {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name))
return
}
-
- utils.WriteResponse(w, http.StatusOK, tree)
+ utils.WriteResponse(w, http.StatusOK, report)
}
func GetImage(w http.ResponseWriter, r *http.Request) {
@@ -341,7 +341,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Reference string `schema:"reference"`
- Credentials string `schema:"credentials"`
OverrideOS string `schema:"overrideOS"`
OverrideArch string `schema:"overrideArch"`
TLSVerify bool `schema:"tlsVerify"`
@@ -384,20 +383,16 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
return
}
- var registryCreds *types.DockerAuthConfig
- if len(query.Credentials) != 0 {
- creds, err := util.ParseRegistryCreds(query.Credentials)
- if err != nil {
- utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
- errors.Wrapf(err, "error parsing credentials %q", query.Credentials))
- return
- }
- registryCreds = creds
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
}
+ defer auth.RemoveAuthfile(authfile)
// Setup the registry options
dockerRegistryOptions := image.DockerRegistryOptions{
- DockerRegistryCreds: registryCreds,
+ DockerRegistryCreds: authConf,
OSChoice: query.OverrideOS,
ArchitectureChoice: query.OverrideArch,
}
@@ -405,6 +400,13 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
+ sys := runtime.SystemContext()
+ if sys == nil {
+ sys = image.GetSystemContext("", authfile, false)
+ }
+ dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
+ sys.DockerAuthConfig = authConf
+
// Prepare the images we want to pull
imagesToPull := []string{}
res := []handlers.LibpodImagesPullReport{}
@@ -413,8 +415,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
if !query.AllTags {
imagesToPull = append(imagesToPull, imageName)
} else {
- systemContext := image.GetSystemContext("", "", false)
- tags, err := docker.GetRepositoryTags(context.Background(), systemContext, imageRef)
+ tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef)
if err != nil {
utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags"))
return
@@ -424,12 +425,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
}
}
- authfile := ""
- if sys := runtime.SystemContext(); sys != nil {
- dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- authfile = sys.AuthFilePath
- }
-
// Finally pull the images
for _, img := range imagesToPull {
newImage, err := runtime.ImageRuntime().New(
@@ -458,7 +453,6 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
- Credentials string `schema:"credentials"`
Destination string `schema:"destination"`
TLSVerify bool `schema:"tlsVerify"`
}{
@@ -494,26 +488,20 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
- var registryCreds *types.DockerAuthConfig
- if len(query.Credentials) != 0 {
- creds, err := util.ParseRegistryCreds(query.Credentials)
- if err != nil {
- utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
- errors.Wrapf(err, "error parsing credentials %q", query.Credentials))
- return
- }
- registryCreds = creds
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
}
+ defer auth.RemoveAuthfile(authfile)
+ logrus.Errorf("AuthConf: %v", authConf)
- // TODO: the X-Registry-Auth header is not checked yet here nor in any other
- // endpoint. Pushing does NOT work with authentication at the moment.
dockerRegistryOptions := &image.DockerRegistryOptions{
- DockerRegistryCreds: registryCreds,
+ DockerRegistryCreds: authConf,
}
- authfile := ""
if sys := runtime.SystemContext(); sys != nil {
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- authfile = sys.AuthFilePath
+ dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
}
if _, found := r.URL.Query()["tlsVerify"]; found {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
@@ -700,8 +688,8 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, reports)
}
-// ImagesRemove is the endpoint for image removal.
-func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+// ImagesBatchRemove is the endpoint for batch image removal.
+func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
@@ -722,7 +710,49 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
imageEngine := abi.ImageEngine{Libpod: runtime}
- rmReport, rmError := imageEngine.Remove(r.Context(), query.Images, opts)
- report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Error: rmError.Error()}
+ rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts)
+
+ strErrs := errorhandling.ErrorsToStrings(rmErrors)
+ report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: strErrs}
utils.WriteResponse(w, http.StatusOK, report)
}
+
+// ImagesRemove is the endpoint for removing one image.
+func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Force bool `schema:"force"`
+ }{
+ Force: false,
+ }
+
+ 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
+ }
+
+ opts := entities.ImageRemoveOptions{Force: query.Force}
+ imageEngine := abi.ImageEngine{Libpod: runtime}
+ rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts)
+
+ // In contrast to batch-removal, where we're only setting the exit
+ // code, we need to have another closer look at the errors here and set
+ // the appropriate http status code.
+
+ switch rmReport.ExitCode {
+ case 0:
+ report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}}
+ utils.WriteResponse(w, http.StatusOK, report)
+ case 1:
+ // 404 - no such image
+ utils.Error(w, "error removing image", http.StatusNotFound, errorhandling.JoinErrors(rmErrors))
+ case 2:
+ // 409 - conflict error (in use by containers)
+ utils.Error(w, "error removing image", http.StatusConflict, errorhandling.JoinErrors(rmErrors))
+ default:
+ // 500 - internal error
+ utils.Error(w, "failed to remove image", http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors))
+ }
+}
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index d87ed7eba..aef92368b 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -120,6 +120,10 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
}
func ManifestPush(w http.ResponseWriter, r *http.Request) {
+ // FIXME: parameters are missing (tlsVerify, format).
+ // Also, we should use the ABI function to avoid duplicate code.
+ // Also, support for XRegistryAuth headers are missing.
+
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
@@ -151,6 +155,7 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) {
}
sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
opts := manifests.PushOptions{
+ Store: runtime.GetStore(),
ImageListSelection: copy2.CopySpecificImages,
SystemContext: sc,
}
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
index e8a92e93e..7de285e5e 100644
--- a/pkg/api/handlers/libpod/networks.go
+++ b/pkg/api/handlers/libpod/networks.go
@@ -1,39 +1,59 @@
package libpod
import (
+ "encoding/json"
"net/http"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/containers/libpod/pkg/network"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
-func CreateNetwork(w http.ResponseWriter, r *http.Request) {}
-func ListNetworks(w http.ResponseWriter, r *http.Request) {
+func CreateNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- config, err := runtime.GetConfig()
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ options := entities.NetworkCreateOptions{}
+ if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
+ utils.Error(w, "unable to marshall input", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ query := struct {
+ Name string `schema:"name"`
+ }{
+ // override any golang type defaults
+ }
+ 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
+ }
+ ic := abi.ContainerEngine{Libpod: runtime}
+ report, err := ic.NetworkCreate(r.Context(), query.Name, options)
if err != nil {
utils.InternalServerError(w, err)
return
}
- configDir := config.Network.NetworkConfigDir
- if len(configDir) < 1 {
- configDir = network.CNIConfigDir
- }
- networks, err := network.LoadCNIConfsFromDir(configDir)
+ utils.WriteResponse(w, http.StatusOK, report)
+
+}
+func ListNetworks(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ options := entities.NetworkListOptions{}
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.NetworkList(r.Context(), options)
if err != nil {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusOK, networks)
+ utils.WriteResponse(w, http.StatusOK, reports)
}
func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
- // 200 ok
- // 404 no such
- // 500 internal
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Force bool `schema:"force"`
@@ -46,22 +66,30 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
return
}
name := utils.GetName(r)
- if err := network.RemoveNetwork(name); err != nil {
+
+ options := entities.NetworkRmOptions{
+ Force: query.Force,
+ }
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.NetworkRm(r.Context(), []string{name}, options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if reports[0].Err != nil {
// If the network cannot be found, we return a 404.
if errors.Cause(err) == network.ErrNetworkNotFound {
utils.Error(w, "Something went wrong", http.StatusNotFound, err)
return
}
- utils.InternalServerError(w, err)
- return
}
- utils.WriteResponse(w, http.StatusOK, "")
+ utils.WriteResponse(w, http.StatusOK, reports)
}
func InspectNetwork(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
- Force bool `schema:"force"`
}{
// override any golang type defaults
}
@@ -71,7 +99,9 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
return
}
name := utils.GetName(r)
- n, err := network.InspectNetwork(name)
+ options := entities.NetworkInspectOptions{}
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.NetworkInspect(r.Context(), []string{name}, options)
if err != nil {
// If the network cannot be found, we return a 404.
if errors.Cause(err) == network.ErrNetworkNotFound {
@@ -81,5 +111,5 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusOK, n)
+ utils.WriteResponse(w, http.StatusOK, reports)
}
diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go
new file mode 100644
index 000000000..1cb5cdb6c
--- /dev/null
+++ b/pkg/api/handlers/libpod/play.go
@@ -0,0 +1,82 @@
+package libpod
+
+import (
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/auth"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func PlayKube(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Network string `schema:"reference"`
+ TLSVerify bool `schema:"tlsVerify"`
+ }{
+ TLSVerify: true,
+ }
+
+ 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
+ }
+
+ // Fetch the K8s YAML file from the body, and copy it to a temp file.
+ tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ tmpfile.Close()
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file"))
+ return
+ }
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+ var username, password string
+ if authConf != nil {
+ username = authConf.Username
+ password = authConf.Password
+ }
+
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ options := entities.PlayKubeOptions{
+ Authfile: authfile,
+ Username: username,
+ Password: password,
+ Network: query.Network,
+ Quiet: true,
+ }
+ if _, found := r.URL.Query()["tlsVerify"]; found {
+ options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, report)
+}
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index 46426eb6b..057fbfb41 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -91,6 +91,34 @@ type swagInfoResponse struct {
Body define.Info
}
+// Network rm
+// swagger:response NetworkRmReport
+type swagNetworkRmReport struct {
+ // in:body
+ Body entities.NetworkRmReport
+}
+
+// Network inspect
+// swagger:response NetworkInspectReport
+type swagNetworkInspectReport struct {
+ // in:body
+ Body []entities.NetworkInspectReport
+}
+
+// Network list
+// swagger:response NetworkListReport
+type swagNetworkListReport struct {
+ // in:body
+ Body []entities.NetworkListReport
+}
+
+// Network create
+// swagger:response NetworkCreateReport
+type swagNetworkCreateReport struct {
+ // in:body
+ Body entities.NetworkCreateReport
+}
+
func ServeSwagger(w http.ResponseWriter, r *http.Request) {
path := DefaultPodmanSwaggerSpec
if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go
index 98e33bf10..f575546c9 100644
--- a/pkg/api/handlers/libpod/system.go
+++ b/pkg/api/handlers/libpod/system.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/pkg/api/handlers/compat"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@@ -69,3 +70,15 @@ func SystemPrune(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, systemPruneReport)
}
+
+func DiskUsage(w http.ResponseWriter, r *http.Request) {
+ // Options are only used by the CLI
+ options := entities.SystemDfOptions{}
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ ic := abi.ContainerEngine{Libpod: runtime}
+ response, err := ic.SystemDf(r.Context(), options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+ utils.WriteResponse(w, http.StatusOK, response)
+}
diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go
index 0aceaf5f6..5d125417b 100644
--- a/pkg/api/handlers/swagger/swagger.go
+++ b/pkg/api/handlers/swagger/swagger.go
@@ -56,6 +56,13 @@ type swagLibpodImagesRemoveResponse struct {
Body handlers.LibpodImagesRemoveReport
}
+// PlayKube response
+// swagger:response DocsLibpodPlayKubeResponse
+type swagLibpodPlayKubeResponse struct {
+ // in:body
+ Body entities.PlayKubeReport
+}
+
// Delete response
// swagger:response DocsImageDeleteResponse
type swagImageDeleteResponse struct {
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 58a12ea6a..aa3d0fe91 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -41,7 +41,7 @@ type LibpodImagesPullReport struct {
type LibpodImagesRemoveReport struct {
entities.ImageRemoveReport
// Image removal requires is to return data and an error.
- Error string
+ Errors []string
}
type ContainersPruneReport struct {
@@ -71,10 +71,6 @@ type Container struct {
docker.ContainerCreateConfig
}
-type Version struct {
- docker.Version
-}
-
type DiskUsage struct {
docker.DiskUsage
}
@@ -124,7 +120,7 @@ type CreateContainerConfig struct {
// swagger:model IDResponse
type IDResponse struct {
// ID
- ID string `json:"id"`
+ ID string `json:"Id"`
}
type ContainerTopOKBody struct {
@@ -174,6 +170,11 @@ type ExecCreateResponse struct {
docker.IDResponse
}
+type ExecStartConfig struct {
+ Detach bool `json:"Detach"`
+ Tty bool `json:"Tty"`
+}
+
func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
containers, err := l.Containers()
if err != nil {
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
index 3253a9be3..c17720694 100644
--- a/pkg/api/handlers/utils/errors.go
+++ b/pkg/api/handlers/utils/errors.go
@@ -63,6 +63,14 @@ func PodNotFound(w http.ResponseWriter, name string, err error) {
Error(w, msg, http.StatusNotFound, err)
}
+func SessionNotFound(w http.ResponseWriter, name string, err error) {
+ if errors.Cause(err) != define.ErrNoSuchExecSession {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such exec session: %s", name)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
func ContainerNotRunning(w http.ResponseWriter, containerID string, err error) {
msg := fmt.Sprintf("Container %s is not running", containerID)
Error(w, msg, http.StatusConflict, err)
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index b5bd488fb..2f4a54b98 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -9,11 +9,55 @@ import (
"os"
"strings"
+ "github.com/blang/semver"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+type (
+ // VersionTree determines which API endpoint tree for version
+ VersionTree int
+ // VersionLevel determines which API level, current or something from the past
+ VersionLevel int
+)
+
+const (
+ // LibpodTree supports Libpod endpoints
+ LibpodTree = VersionTree(iota)
+ // CompatTree supports Libpod endpoints
+ CompatTree
+
+ // CurrentApiVersion announces what is the current API level
+ CurrentApiVersion = VersionLevel(iota)
+ // MinimalApiVersion announces what is the oldest API level supported
+ MinimalApiVersion
+)
+
+var (
+ // See https://docs.docker.com/engine/api/v1.40/
+ // libpod compat handlers are expected to honor docker API versions
+
+ // ApiVersion provides the current and minimal API versions for compat and libpod endpoint trees
+ // Note: GET|HEAD /_ping is never versioned and provides the API-Version and Libpod-API-Version headers to allow
+ // clients to shop for the Version they wish to support
+ ApiVersion = map[VersionTree]map[VersionLevel]semver.Version{
+ LibpodTree: {
+ CurrentApiVersion: semver.MustParse("1.0.0"),
+ MinimalApiVersion: semver.MustParse("1.0.0"),
+ },
+ CompatTree: {
+ CurrentApiVersion: semver.MustParse("1.40.0"),
+ MinimalApiVersion: semver.MustParse("1.24.0"),
+ },
+ }
+
+ // ErrVersionNotGiven returned when version not given by client
+ ErrVersionNotGiven = errors.New("version not given in URL path")
+ // ErrVersionNotSupported returned when given version is too old
+ ErrVersionNotSupported = errors.New("given version is not supported")
+)
+
// IsLibpodRequest returns true if the request related to a libpod endpoint
// (e.g., /v2/libpod/...).
func IsLibpodRequest(r *http.Request) bool {
@@ -21,6 +65,48 @@ func IsLibpodRequest(r *http.Request) bool {
return len(split) >= 3 && split[2] == "libpod"
}
+// SupportedVersion validates that the version provided by client is included in the given condition
+// https://github.com/blang/semver#ranges provides the details for writing conditions
+// If a version is not given in URL path, ErrVersionNotGiven is returned
+func SupportedVersion(r *http.Request, condition string) (semver.Version, error) {
+ version := semver.Version{}
+ val, ok := mux.Vars(r)["version"]
+ if !ok {
+ return version, ErrVersionNotGiven
+ }
+ safeVal, err := url.PathUnescape(val)
+ if err != nil {
+ return version, errors.Wrapf(err, "unable to unescape given API version: %q", val)
+ }
+ version, err = semver.ParseTolerant(safeVal)
+ if err != nil {
+ return version, errors.Wrapf(err, "unable to parse given API version: %q from %q", safeVal, val)
+ }
+
+ inRange, err := semver.ParseRange(condition)
+ if err != nil {
+ return version, err
+ }
+
+ if inRange(version) {
+ return version, nil
+ }
+ return version, ErrVersionNotSupported
+}
+
+// SupportedVersionWithDefaults validates that the version provided by client valid is supported by server
+// minimal API version <= client path version <= maximum API version focused on the endpoint tree from URL
+func SupportedVersionWithDefaults(r *http.Request) (semver.Version, error) {
+ tree := CompatTree
+ if IsLibpodRequest(r) {
+ tree = LibpodTree
+ }
+
+ return SupportedVersion(r,
+ fmt.Sprintf(">=%s <=%s", ApiVersion[tree][MinimalApiVersion].String(),
+ ApiVersion[tree][CurrentApiVersion].String()))
+}
+
// WriteResponse encodes the given value as JSON or string and renders it for http client
func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
// RFC2616 explicitly states that the following status codes "MUST NOT
diff --git a/pkg/api/handlers/utils/handler_test.go b/pkg/api/handlers/utils/handler_test.go
new file mode 100644
index 000000000..6009432b5
--- /dev/null
+++ b/pkg/api/handlers/utils/handler_test.go
@@ -0,0 +1,139 @@
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gorilla/mux"
+)
+
+func TestSupportedVersion(t *testing.T) {
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", ApiVersion[LibpodTree][CurrentApiVersion]),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": ApiVersion[LibpodTree][CurrentApiVersion].String()})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersionWithDefaults(r)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusOK)
+ }
+
+ // Check the response body is what we expect.
+ expected := `OK`
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
+
+func TestUnsupportedVersion(t *testing.T) {
+ version := "999.999.999"
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", version),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": version})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersionWithDefaults(r)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusBadRequest {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusBadRequest)
+ }
+
+ // Check the response body is what we expect.
+ expected := ErrVersionNotSupported.Error()
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
+
+func TestEqualVersion(t *testing.T) {
+ version := "1.30.0"
+ req, err := http.NewRequest("GET",
+ fmt.Sprintf("/v%s/libpod/testing/versions", version),
+ nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req = mux.SetURLVars(req, map[string]string{"version": version})
+
+ rr := httptest.NewRecorder()
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := SupportedVersion(r, "=="+version)
+ switch {
+ case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ case errors.Is(err, ErrVersionNotSupported): // version given but not supported
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprint(w, err.Error())
+ case err != nil:
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprint(w, err.Error())
+ default: // all good
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "OK")
+ }
+ })
+ handler.ServeHTTP(rr, req)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusOK)
+ }
+
+ // Check the response body is what we expect.
+ expected := http.StatusText(http.StatusOK)
+ if rr.Body.String() != expected {
+ t.Errorf("handler returned unexpected body: got %q want %q",
+ rr.Body.String(), expected)
+ }
+}
diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go
index 1c67de9db..7fb31a177 100644
--- a/pkg/api/handlers/utils/images.go
+++ b/pkg/api/handlers/utils/images.go
@@ -62,7 +62,6 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
}{
// This is where you can override the golang default value for one of fields
}
- // TODO I think all is implemented with a filter?
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
return nil, err
@@ -71,6 +70,10 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
if _, found := r.URL.Query()["digests"]; found && query.Digests {
UnSupportedParameter("digests")
}
+ var (
+ images []*image.Image
+ err error
+ )
if len(query.Filters) > 0 {
for k, v := range query.Filters {
@@ -78,11 +81,33 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
filters = append(filters, fmt.Sprintf("%s=%s", k, val))
}
}
- return runtime.ImageRuntime().GetImagesWithFilters(filters)
+ images, err = runtime.ImageRuntime().GetImagesWithFilters(filters)
+ if err != nil {
+ return images, err
+ }
} else {
- return runtime.ImageRuntime().GetImages()
+ images, err = runtime.ImageRuntime().GetImages()
+ if err != nil {
+ return images, err
+ }
}
-
+ if query.All {
+ return images, nil
+ }
+ var returnImages []*image.Image
+ for _, img := range images {
+ if len(img.Names()) == 0 {
+ parent, err := img.IsParent(r.Context())
+ if err != nil {
+ return nil, err
+ }
+ if parent {
+ continue
+ }
+ }
+ returnImages = append(returnImages, img)
+ }
+ return returnImages, nil
}
func GetImage(r *http.Request, name string) (*image.Image, error) {
diff --git a/pkg/api/server/register_archive.go b/pkg/api/server/register_archive.go
new file mode 100644
index 000000000..a1d5941bc
--- /dev/null
+++ b/pkg/api/server/register_archive.go
@@ -0,0 +1,171 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/compat"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerAchiveHandlers(r *mux.Router) error {
+ // swagger:operation POST /containers/{name}/archive compat putArchive
+ // ---
+ // summary: Put files into a container
+ // description: Put a tar archive of files into a container
+ // tags:
+ // - containers (compat)
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // description: container name or id
+ // required: true
+ // - in: query
+ // name: path
+ // type: string
+ // description: Path to a directory in the container to extract
+ // required: true
+ // - in: query
+ // name: noOverwriteDirNonDir
+ // type: string
+ // description: if unpacking the given content would cause an existing directory to be replaced with a non-directory and vice versa (1 or true)
+ // - in: query
+ // name: copyUIDGID
+ // type: string
+ // description: copy UID/GID maps to the dest file or di (1 or true)
+ // - in: body
+ // name: request
+ // description: tarfile of files to copy into the container
+ // schema:
+ // type: string
+ // responses:
+ // 200:
+ // description: no error
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 403:
+ // description: the container rootfs is read-only
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 500:
+ // $ref: "#/responses/InternalError"
+
+ // swagger:operation GET /containers/{name}/archive compat getArchive
+ // ---
+ // summary: Get files from a container
+ // description: Get a tar archive of files from a container
+ // tags:
+ // - containers (compat)
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // description: container name or id
+ // required: true
+ // - in: query
+ // name: path
+ // type: string
+ // description: Path to a directory in the container to extract
+ // required: true
+ // responses:
+ // 200:
+ // description: no error
+ // schema:
+ // type: string
+ // format: binary
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/containers/{name}/archive"), s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPost)
+ // Added non version path to URI to support docker non versioned paths
+ r.HandleFunc("/containers/{name}/archive", s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPost)
+
+ /*
+ Libpod
+ */
+
+ // swagger:operation POST /libpod/containers/{name}/copy libpod libpodPutArchive
+ // ---
+ // summary: Copy files into a container
+ // description: Copy a tar archive of files into a container
+ // tags:
+ // - containers
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // description: container name or id
+ // required: true
+ // - in: query
+ // name: path
+ // type: string
+ // description: Path to a directory in the container to extract
+ // required: true
+ // - in: query
+ // name: pause
+ // type: boolean
+ // description: pause the container while copying (defaults to true)
+ // default: true
+ // - in: body
+ // name: request
+ // description: tarfile of files to copy into the container
+ // schema:
+ // type: string
+ // responses:
+ // 200:
+ // description: no error
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 403:
+ // description: the container rootfs is read-only
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 500:
+ // $ref: "#/responses/InternalError"
+
+ // swagger:operation GET /libpod/containers/{name}/copy libpod libpodGetArchive
+ // ---
+ // summary: Copy files from a container
+ // description: Copy a tar archive of files from a container
+ // tags:
+ // - containers (compat)
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // description: container name or id
+ // required: true
+ // - in: query
+ // name: path
+ // type: string
+ // description: Path to a directory in the container to extract
+ // required: true
+ // responses:
+ // 200:
+ // description: no error
+ // schema:
+ // type: string
+ // format: binary
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/{name}/copy"), s.APIHandler(libpod.Archive)).Methods(http.MethodGet, http.MethodPost)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name}/archive"), s.APIHandler(libpod.Archive)).Methods(http.MethodGet, http.MethodPost)
+
+ return nil
+}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 378d1e06c..0d78e4cdb 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -584,9 +584,9 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// $ref: "#/responses/NoSuchContainer"
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/containers/{name}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
// Added non version path to URI to support docker non versioned paths
- r.HandleFunc("/containers/{name}/resize", s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost)
+ r.HandleFunc("/containers/{name}/resize", s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
// swagger:operation GET /containers/{name}/export compat exportContainer
// ---
// tags:
@@ -1259,7 +1259,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// $ref: "#/responses/NoSuchContainer"
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
// swagger:operation GET /libpod/containers/{name}/export libpod libpodExportContainer
// ---
// tags:
diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go
index e909303da..2b85eb169 100644
--- a/pkg/api/server/register_events.go
+++ b/pkg/api/server/register_events.go
@@ -58,6 +58,11 @@ func (s *APIServer) registerEventsHandlers(r *mux.Router) error {
// type: string
// in: query
// description: JSON encoded map[string][]string of constraints
+ // - name: stream
+ // type: boolean
+ // in: query
+ // default: true
+ // description: when false, do not follow events
// responses:
// 200:
// description: returns a string of json data describing an event
diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go
index 71fb50307..1533edba9 100644
--- a/pkg/api/server/register_exec.go
+++ b/pkg/api/server/register_exec.go
@@ -97,10 +97,10 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error {
// properties:
// Detach:
// type: boolean
- // description: Detach from the command
+ // description: Detach from the command. Not presently supported.
// Tty:
// type: boolean
- // description: Allocate a pseudo-TTY
+ // description: Allocate a pseudo-TTY. Presently ignored.
// produces:
// - application/json
// responses:
@@ -109,12 +109,12 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error {
// 404:
// $ref: "#/responses/NoSuchExecInstance"
// 409:
- // description: container is stopped or paused
+ // description: container is not running
// 500:
// $ref: "#/responses/InternalError"
- r.Handle(VersionedPath("/exec/{id}/start"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
+ r.Handle(VersionedPath("/exec/{id}/start"), s.APIHandler(compat.ExecStartHandler)).Methods(http.MethodPost)
// Added non version path to URI to support docker non versioned paths
- r.Handle("/exec/{id}/start", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
+ r.Handle("/exec/{id}/start", s.APIHandler(compat.ExecStartHandler)).Methods(http.MethodPost)
// swagger:operation POST /exec/{id}/resize compat resizeExec
// ---
// tags:
@@ -145,15 +145,15 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error {
// $ref: "#/responses/NoSuchExecInstance"
// 500:
// $ref: "#/responses/InternalError"
- r.Handle(VersionedPath("/exec/{id}/resize"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
+ r.Handle(VersionedPath("/exec/{id}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
// Added non version path to URI to support docker non versioned paths
- r.Handle("/exec/{id}/resize", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
+ r.Handle("/exec/{id}/resize", s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
// swagger:operation GET /exec/{id}/json compat inspectExec
// ---
// tags:
// - exec (compat)
// summary: Inspect an exec instance
- // description: Return low-level information about an exec instance.
+ // description: Return low-level information about an exec instance. Stale (stopped) exec sessions will be auto-removed after inspect runs.
// parameters:
// - in: path
// name: id
@@ -264,10 +264,10 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error {
// properties:
// Detach:
// type: boolean
- // description: Detach from the command
+ // description: Detach from the command. Not presently supported.
// Tty:
// type: boolean
- // description: Allocate a pseudo-TTY
+ // description: Allocate a pseudo-TTY. Presently ignored.
// produces:
// - application/json
// responses:
@@ -276,10 +276,10 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error {
// 404:
// $ref: "#/responses/NoSuchExecInstance"
// 409:
- // description: container is stopped or paused
+ // description: container is not running.
// 500:
// $ref: "#/responses/InternalError"
- r.Handle(VersionedPath("/libpod/exec/{id}/start"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost)
+ r.Handle(VersionedPath("/libpod/exec/{id}/start"), s.APIHandler(compat.ExecStartHandler)).Methods(http.MethodPost)
// swagger:operation POST /libpod/exec/{id}/resize libpod libpodResizeExec
// ---
// tags:
diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go
new file mode 100644
index 000000000..391e60111
--- /dev/null
+++ b/pkg/api/server/register_generate.go
@@ -0,0 +1,41 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
+ // swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube
+ // ---
+ // tags:
+ // - containers
+ // - pods
+ // summary: Play a Kubernetes YAML file.
+ // description: Create and run pods based on a Kubernetes YAML file (pod or service kind).
+ // parameters:
+ // - in: path
+ // name: name:.*
+ // type: string
+ // required: true
+ // description: Name or ID of the container or pod.
+ // - in: query
+ // name: service
+ // type: boolean
+ // default: false
+ // description: Generate YAML for a Kubernetes service object.
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // description: no error
+ // schema:
+ // type: string
+ // format: binary
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index f59dca6f5..83584d0f3 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -8,6 +8,10 @@ import (
"github.com/gorilla/mux"
)
+// TODO
+//
+// * /images/create is missing the "message" and "platform" parameters
+
func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// swagger:operation POST /images/create compat createImage
// ---
@@ -631,13 +635,14 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// required: true
// description: Name of image to push.
// - in: query
- // name: tag
+ // name: destination
// type: string
- // description: The tag to associate with the image on the registry.
+ // description: Allows for pushing the image to a different destintation than the image refers to.
// - in: query
- // name: credentials
- // description: username:password for the registry.
- // type: string
+ // name: tlsVerify
+ // description: Require TLS verification.
+ // type: boolean
+ // default: true
// - in: header
// name: X-Registry-Auth
// type: string
@@ -698,7 +703,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// responses:
// 200:
// $ref: '#/responses/LibpodImageTreeResponse'
- // 401:
+ // 404:
// $ref: '#/responses/NoSuchImage'
// 500:
// $ref: '#/responses/InternalError'
@@ -822,7 +827,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost)
- // swagger:operation GET /libpod/images/remove libpod libpodImagesRemove
+ // swagger:operation DELETE /libpod/images/remove libpod libpodImagesRemove
// ---
// tags:
// - images
@@ -853,7 +858,37 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// $ref: "#/responses/BadParamError"
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodGet)
+ r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesBatchRemove)).Methods(http.MethodDelete)
+ // swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage
+ // ---
+ // tags:
+ // - images
+ // summary: Remove an image from the local storage.
+ // description: Remove an image from the local storage.
+ // parameters:
+ // - in: path
+ // name: name:.*
+ // type: string
+ // required: true
+ // description: name or ID of image to remove
+ // - in: query
+ // name: force
+ // type: boolean
+ // description: remove the image even if used by containers or has other tags
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: '#/responses/NoSuchImage'
+ // 409:
+ // $ref: '#/responses/ConflictError'
+ // 500:
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:.*}"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodDelete)
// swagger:operation POST /libpod/images/pull libpod libpodImagesPull
// ---
// tags:
@@ -952,36 +987,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(libpod.SearchImages)).Methods(http.MethodGet)
- // swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage
- // ---
- // tags:
- // - images
- // summary: Remove Image
- // description: Delete an image from local store
- // parameters:
- // - in: path
- // name: name:.*
- // type: string
- // required: true
- // description: name or ID of image to delete
- // - in: query
- // name: force
- // type: boolean
- // description: remove the image even if used by containers or has other tags
- // produces:
- // - application/json
- // responses:
- // 200:
- // $ref: "#/responses/DocsImageDeleteResponse"
- // 400:
- // $ref: "#/responses/BadParamError"
- // 404:
- // $ref: '#/responses/NoSuchImage'
- // 409:
- // $ref: '#/responses/ConflictError'
- // 500:
- // $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/libpod/images/{name:.*}"), s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete)
// swagger:operation GET /libpod/images/{name:.*}/get libpod libpodExportImage
// ---
// tags:
@@ -1188,5 +1193,214 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/images/{name}/changes"), s.APIHandler(compat.Changes)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/build libpod libpodBuildImage
+ // ---
+ // tags:
+ // - images
+ // summary: Create image
+ // description: Build an image from the given Dockerfile(s)
+ // parameters:
+ // - in: query
+ // name: dockerfile
+ // type: string
+ // default: Dockerfile
+ // description: |
+ // Path within the build context to the `Dockerfile`.
+ // This is ignored if remote is specified and points to an external `Dockerfile`.
+ // - in: query
+ // name: t
+ // type: string
+ // default: latest
+ // description: A name and optional tag to apply to the image in the `name:tag` format.
+ // - in: query
+ // name: extrahosts
+ // type: string
+ // default:
+ // description: |
+ // TBD Extra hosts to add to /etc/hosts
+ // (As of version 1.xx)
+ // - in: query
+ // name: remote
+ // type: string
+ // default:
+ // description: |
+ // A Git repository URI or HTTP/HTTPS context URI.
+ // If the URI points to a single text file, the file’s contents are placed
+ // into a file called Dockerfile and the image is built from that file. If
+ // the URI points to a tarball, the file is downloaded by the daemon and the
+ // contents therein used as the context for the build. If the URI points to a
+ // tarball and the dockerfile parameter is also specified, there must be a file
+ // with the corresponding path inside the tarball.
+ // (As of version 1.xx)
+ // - in: query
+ // name: q
+ // type: boolean
+ // default: false
+ // description: |
+ // Suppress verbose build output
+ // - in: query
+ // name: nocache
+ // type: boolean
+ // default: false
+ // description: |
+ // Do not use the cache when building the image
+ // (As of version 1.xx)
+ // - in: query
+ // name: cachefrom
+ // type: string
+ // default:
+ // description: |
+ // JSON array of images used to build cache resolution
+ // (As of version 1.xx)
+ // - in: query
+ // name: pull
+ // type: boolean
+ // default: false
+ // description: |
+ // Attempt to pull the image even if an older image exists locally
+ // (As of version 1.xx)
+ // - in: query
+ // name: rm
+ // type: boolean
+ // default: true
+ // description: |
+ // Remove intermediate containers after a successful build
+ // (As of version 1.xx)
+ // - in: query
+ // name: forcerm
+ // type: boolean
+ // default: false
+ // description: |
+ // Always remove intermediate containers, even upon failure
+ // (As of version 1.xx)
+ // - in: query
+ // name: memory
+ // type: integer
+ // description: |
+ // Memory is the upper limit (in bytes) on how much memory running containers can use
+ // (As of version 1.xx)
+ // - in: query
+ // name: memswap
+ // type: integer
+ // description: |
+ // MemorySwap limits the amount of memory and swap together
+ // (As of version 1.xx)
+ // - in: query
+ // name: cpushares
+ // type: integer
+ // description: |
+ // CPUShares (relative weight
+ // (As of version 1.xx)
+ // - in: query
+ // name: cpusetcpus
+ // type: string
+ // description: |
+ // CPUSetCPUs in which to allow execution (0-3, 0,1)
+ // (As of version 1.xx)
+ // - in: query
+ // name: cpuperiod
+ // type: integer
+ // description: |
+ // CPUPeriod limits the CPU CFS (Completely Fair Scheduler) period
+ // (As of version 1.xx)
+ // - in: query
+ // name: cpuquota
+ // type: integer
+ // description: |
+ // CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota
+ // (As of version 1.xx)
+ // - in: query
+ // name: buildargs
+ // type: string
+ // default:
+ // description: |
+ // JSON map of string pairs denoting build-time variables.
+ // For example, the build argument `Foo` with the value of `bar` would be encoded in JSON as `["Foo":"bar"]`.
+ //
+ // For example, buildargs={"Foo":"bar"}.
+ //
+ // Note(s):
+ // * This should not be used to pass secrets.
+ // * The value of buildargs should be URI component encoded before being passed to the API.
+ //
+ // (As of version 1.xx)
+ // - in: query
+ // name: shmsize
+ // type: integer
+ // default: 67108864
+ // description: |
+ // ShmSize is the "size" value to use when mounting an shmfs on the container's /dev/shm directory.
+ // Default is 64MB
+ // (As of version 1.xx)
+ // - in: query
+ // name: squash
+ // type: boolean
+ // default: false
+ // description: |
+ // Silently ignored.
+ // Squash the resulting images layers into a single layer
+ // (As of version 1.xx)
+ // - in: query
+ // name: labels
+ // type: string
+ // default:
+ // description: |
+ // JSON map of key, value pairs to set as labels on the new image
+ // (As of version 1.xx)
+ // - in: query
+ // name: networkmode
+ // type: string
+ // default: bridge
+ // description: |
+ // Sets the networking mode for the run commands during build.
+ // Supported standard values are:
+ // * `bridge` limited to containers within a single host, port mapping required for external access
+ // * `host` no isolation between host and containers on this network
+ // * `none` disable all networking for this container
+ // * container:<nameOrID> share networking with given container
+ // ---All other values are assumed to be a custom network's name
+ // (As of version 1.xx)
+ // - in: query
+ // name: platform
+ // type: string
+ // default:
+ // description: |
+ // Platform format os[/arch[/variant]]
+ // (As of version 1.xx)
+ // - in: query
+ // name: target
+ // type: string
+ // default:
+ // description: |
+ // Target build stage
+ // (As of version 1.xx)
+ // - in: query
+ // name: outputs
+ // type: string
+ // default:
+ // description: |
+ // output configuration TBD
+ // (As of version 1.xx)
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // description: OK (As of version 1.xx)
+ // schema:
+ // type: object
+ // required:
+ // - stream
+ // properties:
+ // stream:
+ // type: string
+ // description: output from build process
+ // example: |
+ // (build details...)
+ // Successfully built 8ba084515c724cbf90d447a63600c0a6
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/build"), s.APIHandler(compat.BuildImage)).Methods(http.MethodPost)
return nil
}
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
new file mode 100644
index 000000000..2c60b2b27
--- /dev/null
+++ b/pkg/api/server/register_networks.go
@@ -0,0 +1,190 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/compat"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
+ // swagger:operation DELETE /networks/{name} compat compatRemoveNetwork
+ // ---
+ // tags:
+ // - networks (compat)
+ // summary: Remove a network
+ // description: Remove a network
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name of the network
+ // produces:
+ // - application/json
+ // responses:
+ // 204:
+ // description: no error
+ // 404:
+ // $ref: "#/responses/NoSuchNetwork"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/networks/{name}"), s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete)
+ r.HandleFunc("/networks/{name}", s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete)
+ // swagger:operation GET /networks/{name}/json compat compatInspectNetwork
+ // ---
+ // tags:
+ // - networks (compat)
+ // summary: Inspect a network
+ // description: Display low level configuration network
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name of the network
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/CompatNetworkInspect"
+ // 404:
+ // $ref: "#/responses/NoSuchNetwork"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/networks/{name}/json"), s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
+ r.HandleFunc("/networks/{name}/json", s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
+ // swagger:operation GET /networks/json compat compatListNetwork
+ // ---
+ // tags:
+ // - networks (compat)
+ // summary: List networks
+ // description: Display summary of network configurations
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/CompatNetworkList"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/networks/json"), s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet)
+ r.HandleFunc("/networks", s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet)
+ // swagger:operation POST /networks/create compat compatCreateNetwork
+ // ---
+ // tags:
+ // - networks (compat)
+ // summary: Create network
+ // description: Create a network configuration
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: body
+ // name: create
+ // description: attributes for creating a container
+ // schema:
+ // $ref: "#/definitions/NetworkCreateRequest"
+ // responses:
+ // 200:
+ // $ref: "#/responses/CompatNetworkCreate"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/networks/create"), s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost)
+ r.HandleFunc("/networks/create", s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost)
+ // swagger:operation DELETE /libpod/networks/{name} libpod libpodRemoveNetwork
+ // ---
+ // tags:
+ // - networks
+ // summary: Remove a network
+ // description: Remove a CNI configured network
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name of the network
+ // - in: query
+ // name: Force
+ // type: boolean
+ // description: remove containers associated with network
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/NetworkRmReport"
+ // 404:
+ // $ref: "#/responses/NoSuchNetwork"
+ // 500:
+ // $ref: "#/responses/InternalError"
+
+ /*
+ Libpod
+ */
+
+ r.HandleFunc(VersionedPath("/libpod/networks/{name}"), s.APIHandler(libpod.RemoveNetwork)).Methods(http.MethodDelete)
+ // swagger:operation GET /libpod/networks/{name}/json libpod libpodInspectNetwork
+ // ---
+ // tags:
+ // - networks
+ // summary: Inspect a network
+ // description: Display low level configuration for a CNI network
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name of the network
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/NetworkInspectReport"
+ // 404:
+ // $ref: "#/responses/NoSuchNetwork"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/networks/{name}/json"), s.APIHandler(libpod.InspectNetwork)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/networks/json libpod libpodListNetwork
+ // ---
+ // tags:
+ // - networks
+ // summary: List networks
+ // description: Display summary of network configurations
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/NetworkListReport"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/networks/json"), s.APIHandler(libpod.ListNetworks)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/networks/create libpod libpodCreateNetwork
+ // ---
+ // tags:
+ // - networks
+ // summary: Create network
+ // description: Create a new CNI network configuration
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: query
+ // name: name
+ // type: string
+ // description: optional name for new network
+ // - in: body
+ // name: create
+ // description: attributes for creating a container
+ // schema:
+ // $ref: "#/definitions/NetworkCreateOptions"
+ // responses:
+ // 200:
+ // $ref: "#/responses/NetworkCreateReport"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/networks/create"), s.APIHandler(libpod.CreateNetwork)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go
new file mode 100644
index 000000000..d04879c19
--- /dev/null
+++ b/pkg/api/server/register_play.go
@@ -0,0 +1,42 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
+ // swagger:operation POST /libpod/play/kube libpod libpodPlayKube
+ // ---
+ // tags:
+ // - containers
+ // - pods
+ // summary: Play a Kubernetes YAML file.
+ // description: Create and run pods based on a Kubernetes YAML file (pod or service kind).
+ // parameters:
+ // - in: query
+ // name: network
+ // type: string
+ // description: Connect the pod to this network.
+ // - in: query
+ // name: tlsVerify
+ // type: boolean
+ // default: true
+ // description: Require HTTPS and verify signatures when contating registries.
+ // - in: body
+ // name: request
+ // description: Kubernetes YAML file.
+ // schema:
+ // type: string
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/DocsLibpodPlayKubeResponse"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKube)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/register_system.go b/pkg/api/server/register_system.go
index 7375a75c1..118ad2d08 100644
--- a/pkg/api/server/register_system.go
+++ b/pkg/api/server/register_system.go
@@ -12,7 +12,7 @@ func (s *APIServer) registerSystemHandlers(r *mux.Router) error {
r.Handle(VersionedPath("/system/df"), s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet)
// Added non version path to URI to support docker non versioned paths
r.Handle("/system/df", s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet)
- // Swagger:operation POST /libpod/system/prune libpod pruneSystem
+ // swagger:operation POST /libpod/system/prune libpod pruneSystem
// ---
// tags:
// - system
@@ -27,6 +27,19 @@ func (s *APIServer) registerSystemHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/system/prune"), s.APIHandler(libpod.SystemPrune)).Methods(http.MethodPost)
-
+ // swagger:operation GET /libpod/system/df libpod df
+ // ---
+ // tags:
+ // - system
+ // summary: Show disk usage
+ // description: Return information about disk usage for containers, images, and volumes
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: '#/responses/SystemDiskUse'
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/system/df"), s.APIHandler(libpod.DiskUsage)).Methods(http.MethodGet)
return nil
}
diff --git a/pkg/api/server/register_version.go b/pkg/api/server/register_version.go
index 25cacbc61..30289ffe3 100644
--- a/pkg/api/server/register_version.go
+++ b/pkg/api/server/register_version.go
@@ -8,7 +8,28 @@ import (
)
func (s *APIServer) registerVersionHandlers(r *mux.Router) error {
+ // swagger:operation GET /version compat CompatSystemVersion
+ // ---
+ // summary: Component Version information
+ // tags:
+ // - system (compat)
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/Version"
r.Handle("/version", s.APIHandler(compat.VersionHandler)).Methods(http.MethodGet)
r.Handle(VersionedPath("/version"), s.APIHandler(compat.VersionHandler)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/version libpod SystemVersion
+ // ---
+ // summary: Component Version information
+ // tags:
+ // - system
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/Version"
+ r.Handle(VersionedPath("/libpod/version"), s.APIHandler(compat.VersionHandler)).Methods(http.MethodGet)
return nil
}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index ce2d152e0..499a4c58a 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -92,18 +92,30 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
},
)
+ router.MethodNotAllowedHandler = http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ // We can track user errors...
+ logrus.Infof("Failed Request: (%d:%s) for %s:'%s'", http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed), r.Method, r.URL.String())
+ http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
+ },
+ )
+
for _, fn := range []func(*mux.Router) error{
server.registerAuthHandlers,
+ server.registerAchiveHandlers,
server.registerContainersHandlers,
server.registerDistributionHandlers,
server.registerEventsHandlers,
server.registerExecHandlers,
+ server.registerGenerateHandlers,
server.registerHealthCheckHandlers,
server.registerImagesHandlers,
server.registerInfoHandlers,
server.registerManifestHandlers,
server.registerMonitorHandlers,
+ server.registerNetworkHandlers,
server.registerPingHandlers,
+ server.registerPlayHandlers,
server.registerPluginsHandlers,
server.registerPodsHandlers,
server.RegisterSwaggerHandlers,
diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go
index 75dcc71a6..c463f809e 100644
--- a/pkg/api/server/swagger.go
+++ b/pkg/api/server/swagger.go
@@ -24,6 +24,15 @@ type swagErrNoSuchContainer struct {
}
}
+// No such network
+// swagger:response NoSuchNetwork
+type swagErrNoSuchNetwork struct {
+ // in:body
+ Body struct {
+ entities.ErrorModel
+ }
+}
+
// No such exec instance
// swagger:response NoSuchExecInstance
type swagErrNoSuchExecInstance struct {
@@ -130,6 +139,13 @@ type swagImageSummary struct {
Body []entities.ImageSummary
}
+// Registries summary
+// swagger:response DocsRegistriesList
+type swagRegistriesList struct {
+ // in:body
+ Body entities.ListRegistriesReport
+}
+
// List Containers
// swagger:response DocsListContainer
type swagListContainers struct {
@@ -181,3 +197,30 @@ type swagHealthCheckRunResponse struct {
define.HealthCheckResults
}
}
+
+// Version
+// swagger:response Version
+type swagVersion struct {
+ // in:body
+ Body struct {
+ entities.SystemVersionReport
+ }
+}
+
+// Disk usage
+// swagger:response SystemDiskUse
+type swagDiskUseResponse struct {
+ // in:body
+ Body struct {
+ entities.SystemDfReport
+ }
+}
+
+// Prune report
+// swagger:response SystemPruneReport
+type swagSystemPruneReport struct {
+ // in:body
+ Body struct {
+ entities.SystemPruneReport
+ }
+}
diff --git a/pkg/api/tags.yaml b/pkg/api/tags.yaml
index 5b5d9f5bb..f86f8dbea 100644
--- a/pkg/api/tags.yaml
+++ b/pkg/api/tags.yaml
@@ -5,9 +5,11 @@ tags:
description: Actions related to exec
- name: images
description: Actions related to images
- - name: pods
- description: Actions related to manifests
- name: manifests
+ description: Actions related to manifests
+ - name: networks
+ description: Actions related to networks
+ - name: pods
description: Actions related to pods
- name: volumes
description: Actions related to volumes
@@ -19,5 +21,7 @@ tags:
description: Actions related to exec for the compatibility endpoints
- name: images (compat)
description: Actions related to images for the compatibility endpoints
+ - name: networks (compat)
+ description: Actions related to compatibility networks
- name: system (compat)
description: Actions related to Podman and compatibility engines
diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go
new file mode 100644
index 000000000..ffa65f7e5
--- /dev/null
+++ b/pkg/auth/auth.go
@@ -0,0 +1,216 @@
+package auth
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strings"
+
+ imageAuth "github.com/containers/image/v5/pkg/docker/config"
+ "github.com/containers/image/v5/types"
+ dockerAPITypes "github.com/docker/docker/api/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// XRegistryAuthHeader is the key to the encoded registry authentication
+// configuration in an http-request header.
+const XRegistryAuthHeader = "X-Registry-Auth"
+
+// GetCredentials extracts one or more DockerAuthConfigs from the request's
+// header. The header could specify a single-auth config in which case the
+// first return value is set. In case of a multi-auth header, the contents are
+// stored in a temporary auth file (2nd return value). Note that the auth file
+// should be removed after usage.
+func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) {
+ authHeader := r.Header.Get(XRegistryAuthHeader)
+ if len(authHeader) == 0 {
+ return nil, "", nil
+ }
+
+ // First look for a multi-auth header (i.e., a map).
+ authConfigs, err := multiAuthHeader(r)
+ if err == nil {
+ authfile, err := authConfigsToAuthFile(authConfigs)
+ return nil, authfile, err
+ }
+
+ // Fallback to looking for a single-auth header (i.e., one config).
+ authConfigs, err = singleAuthHeader(r)
+ if err != nil {
+ return nil, "", err
+ }
+ var conf *types.DockerAuthConfig
+ for k := range authConfigs {
+ c := authConfigs[k]
+ conf = &c
+ break
+ }
+ return conf, "", nil
+}
+
+// Header returns a map with the XRegistryAuthHeader set which can
+// conveniently be used in the http stack.
+func Header(sys *types.SystemContext, authfile, username, password string) (map[string]string, error) {
+ var content string
+ var err error
+
+ if username != "" {
+ content, err = encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password})
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ if sys == nil {
+ sys = &types.SystemContext{}
+ }
+ if authfile != "" {
+ sys.AuthFilePath = authfile
+ }
+ authConfigs, err := imageAuth.GetAllCredentials(sys)
+ if err != nil {
+ return nil, err
+ }
+ content, err = encodeMultiAuthConfigs(authConfigs)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ header := make(map[string]string)
+ header[XRegistryAuthHeader] = content
+
+ return header, nil
+}
+
+// RemoveAuthfile is a convenience function that is meant to be called in a
+// deferred statement. If non-empty, it removes the specified authfile and log
+// errors. It's meant to reduce boilerplate code at call sites of
+// `GetCredentials`.
+func RemoveAuthfile(authfile string) {
+ if authfile == "" {
+ return
+ }
+ if err := os.Remove(authfile); err != nil {
+ logrus.Errorf("Error removing temporary auth file %q: %v", authfile, err)
+ }
+}
+
+// encodeSingleAuthConfig serializes the auth configuration as a base64 encoded JSON payload.
+func encodeSingleAuthConfig(authConfig types.DockerAuthConfig) (string, error) {
+ conf := imageAuthToDockerAuth(authConfig)
+ buf, err := json.Marshal(conf)
+ if err != nil {
+ return "", err
+ }
+ return base64.URLEncoding.EncodeToString(buf), nil
+}
+
+// encodeMultiAuthConfigs serializes the auth configurations as a base64 encoded JSON payload.
+func encodeMultiAuthConfigs(authConfigs map[string]types.DockerAuthConfig) (string, error) {
+ confs := make(map[string]dockerAPITypes.AuthConfig)
+ for registry, authConf := range authConfigs {
+ confs[registry] = imageAuthToDockerAuth(authConf)
+ }
+ buf, err := json.Marshal(confs)
+ if err != nil {
+ return "", err
+ }
+ return base64.URLEncoding.EncodeToString(buf), nil
+}
+
+// authConfigsToAuthFile stores the specified auth configs in a temporary files
+// and returns its path. The file can later be used an auth file for contacting
+// one or more container registries. If tmpDir is empty, the system's default
+// TMPDIR will be used.
+func authConfigsToAuthFile(authConfigs map[string]types.DockerAuthConfig) (string, error) {
+ // Intitialize an empty temporary JSON file.
+ tmpFile, err := ioutil.TempFile("", "auth.json.")
+ if err != nil {
+ return "", err
+ }
+ if _, err := tmpFile.Write([]byte{'{', '}'}); err != nil {
+ return "", errors.Wrap(err, "error initializing temporary auth file")
+ }
+ if err := tmpFile.Close(); err != nil {
+ return "", errors.Wrap(err, "error closing temporary auth file")
+ }
+ authFilePath := tmpFile.Name()
+
+ // TODO: It would be nice if c/image could dump the map at once.
+ //
+ // Now use the c/image packages to store the credentials. It's battle
+ // tested, and we make sure to use the same code as the image backend.
+ sys := types.SystemContext{AuthFilePath: authFilePath}
+ for server, config := range authConfigs {
+ // Note that we do not validate the credentials here. Wassume
+ // that all credentials are valid. They'll be used on demand
+ // later.
+ if err := imageAuth.SetAuthentication(&sys, server, config.Username, config.Password); err != nil {
+ return "", errors.Wrapf(err, "error storing credentials in temporary auth file (server: %q, user: %q)", server, config.Username)
+ }
+ }
+
+ return authFilePath, nil
+}
+
+// dockerAuthToImageAuth converts a docker auth config to one we're using
+// internally from c/image. Note that the Docker types look slightly
+// different, so we need to convert to be extra sure we're not running into
+// undesired side-effects when unmarhalling directly to our types.
+func dockerAuthToImageAuth(authConfig dockerAPITypes.AuthConfig) types.DockerAuthConfig {
+ return types.DockerAuthConfig{
+ Username: authConfig.Username,
+ Password: authConfig.Password,
+ IdentityToken: authConfig.IdentityToken,
+ }
+}
+
+// reverse conversion of `dockerAuthToImageAuth`.
+func imageAuthToDockerAuth(authConfig types.DockerAuthConfig) dockerAPITypes.AuthConfig {
+ return dockerAPITypes.AuthConfig{
+ Username: authConfig.Username,
+ Password: authConfig.Password,
+ IdentityToken: authConfig.IdentityToken,
+ }
+}
+
+// singleAuthHeader extracts a DockerAuthConfig from the request's header.
+// The header content is a single DockerAuthConfig.
+func singleAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) {
+ authHeader := r.Header.Get(XRegistryAuthHeader)
+ authConfig := dockerAPITypes.AuthConfig{}
+ if len(authHeader) > 0 {
+ authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
+ if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil {
+ return nil, err
+ }
+ }
+ authConfigs := make(map[string]types.DockerAuthConfig)
+ authConfigs["0"] = dockerAuthToImageAuth(authConfig)
+ return authConfigs, nil
+}
+
+// multiAuthHeader extracts a DockerAuthConfig from the request's header.
+// The header content is a map[string]DockerAuthConfigs.
+func multiAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) {
+ authHeader := r.Header.Get(XRegistryAuthHeader)
+ if len(authHeader) == 0 {
+ return nil, nil
+ }
+
+ dockerAuthConfigs := make(map[string]dockerAPITypes.AuthConfig)
+ authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
+ if err := json.NewDecoder(authJSON).Decode(&dockerAuthConfigs); err != nil {
+ return nil, err
+ }
+
+ // Now convert to the internal types.
+ authConfigs := make(map[string]types.DockerAuthConfig)
+ for server := range dockerAuthConfigs {
+ authConfigs[server] = dockerAuthToImageAuth(dockerAuthConfigs[server])
+ }
+ return authConfigs, nil
+}
diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go
index 78d5ac474..eca5c342c 100644
--- a/pkg/autoupdate/autoupdate.go
+++ b/pkg/autoupdate/autoupdate.go
@@ -23,6 +23,10 @@ import (
// container labels.
const Label = "io.containers.autoupdate"
+// Label denotes the container label key to specify authfile in
+// container labels.
+const AuthfileLabel = "io.containers.autoupdate.authfile"
+
// Policy represents an auto-update policy.
type Policy string
@@ -63,6 +67,12 @@ func LookupPolicy(s string) (Policy, error) {
return "", errors.Errorf("invalid auto-update policy %q: valid policies are %+q", s, keys)
}
+// Options include parameters for auto updates.
+type Options struct {
+ // Authfile to use when contacting registries.
+ Authfile string
+}
+
// 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
@@ -96,7 +106,7 @@ func ValidateImageReference(imageName string) error {
//
// 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) {
+func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) {
// Create a map from `image ID -> []*Container`.
containerMap, errs := imageContainersMap(runtime)
if len(containerMap) == 0 {
@@ -138,7 +148,12 @@ func AutoUpdate(runtime *libpod.Runtime) ([]string, []error) {
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)
+ labels := ctr.Labels()
+ authFilePath, exists := labels[AuthfileLabel]
+ if exists {
+ options.Authfile = authFilePath
+ }
+ needsUpdate, err := newerImageAvailable(runtime, image, rawImageName, options)
if err != nil {
errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", ctr.ID(), rawImageName))
continue
@@ -148,7 +163,7 @@ func AutoUpdate(runtime *libpod.Runtime) ([]string, []error) {
}
logrus.Infof("Auto-updating container %q using image %q", ctr.ID(), rawImageName)
if _, updated := updatedRawImages[rawImageName]; !updated {
- _, err = updateImage(runtime, rawImageName)
+ _, err = updateImage(runtime, rawImageName, options)
if err != nil {
errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image update for %q failed", ctr.ID(), rawImageName))
continue
@@ -230,13 +245,15 @@ func imageContainersMap(runtime *libpod.Runtime) (map[string][]*libpod.Container
// 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) {
+func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string, options Options) (bool, error) {
remoteRef, err := docker.ParseReference("//" + origName)
if err != nil {
return false, err
}
- remoteImg, err := remoteRef.NewImage(context.Background(), runtime.SystemContext())
+ sys := runtime.SystemContext()
+ sys.AuthFilePath = options.Authfile
+ remoteImg, err := remoteRef.NewImage(context.Background(), sys)
if err != nil {
return false, err
}
@@ -255,25 +272,22 @@ func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName str
}
// updateImage pulls the specified image.
-func updateImage(runtime *libpod.Runtime, name string) (*image.Image, error) {
+func updateImage(runtime *libpod.Runtime, name string, options Options) (*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,
+ options.Authfile,
os.Stderr,
&registryOpts,
image.SigningOptions{},
diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go
index 4b07847d1..7e2a444bd 100644
--- a/pkg/bindings/bindings.go
+++ b/pkg/bindings/bindings.go
@@ -8,11 +8,20 @@
package bindings
+import (
+ "github.com/blang/semver"
+)
+
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
+ pTrue = true
+ PTrue = &pTrue
// PFalse is a convenience variable that can be used in bindings where
// a pointer to a bool (optional parameter) is required.
- PFalse bool = false
+ pFalse = false
+ PFalse = &pFalse
+
+ // _*YES*- podman will fail to run if this value is wrong
+ APIVersion = semver.MustParse("1.0.0")
)
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index da3755fc8..e9032f083 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -15,7 +15,7 @@ import (
"strings"
"time"
- "github.com/containers/libpod/pkg/api/types"
+ "github.com/blang/semver"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -27,7 +27,7 @@ var (
basePath = &url.URL{
Scheme: "http",
Host: "d",
- Path: "/v" + types.MinimalAPIVersion + "/libpod",
+ Path: "/v" + APIVersion.String() + "/libpod",
}
)
@@ -39,6 +39,7 @@ type APIResponse struct {
type Connection struct {
_url *url.URL
client *http.Client
+ conn *net.Conn
}
type valueKey string
@@ -88,26 +89,26 @@ func NewConnection(ctx context.Context, uri string, identity ...string) (context
}
// Now we setup the http client to use the connection above
- var client *http.Client
+ var connection Connection
switch _url.Scheme {
case "ssh":
secure, err = strconv.ParseBool(_url.Query().Get("secure"))
if err != nil {
secure = false
}
- client, err = sshClient(_url, identity[0], secure)
+ connection, err = sshClient(_url, identity[0], secure)
case "unix":
if !strings.HasPrefix(uri, "unix:///") {
// autofix unix://path_element vs unix:///path_element
_url.Path = JoinURL(_url.Host, _url.Path)
_url.Host = ""
}
- client, err = unixClient(_url)
+ connection, err = unixClient(_url)
case "tcp":
if !strings.HasPrefix(uri, "tcp://") {
return nil, errors.New("tcp URIs should begin with tcp://")
}
- client, err = tcpClient(_url)
+ connection, err = tcpClient(_url)
default:
return nil, errors.Errorf("'%s' is not a supported schema", _url.Scheme)
}
@@ -115,46 +116,71 @@ func NewConnection(ctx context.Context, uri string, identity ...string) (context
return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme)
}
- ctx = context.WithValue(ctx, clientKey, &Connection{_url, client})
+ ctx = context.WithValue(ctx, clientKey, &connection)
if err := pingNewConnection(ctx); err != nil {
return nil, err
}
return ctx, nil
}
-func tcpClient(_url *url.URL) (*http.Client, error) {
- return &http.Client{
+func tcpClient(_url *url.URL) (Connection, error) {
+ connection := Connection{
+ _url: _url,
+ }
+ connection.client = &http.Client{
Transport: &http.Transport{
- DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
- return net.Dial("tcp", _url.Host)
+ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
+ conn, err := net.Dial("tcp", _url.Host)
+ if c, ok := ctx.Value(clientKey).(*Connection); ok {
+ c.conn = &conn
+ }
+ return conn, err
},
DisableCompression: true,
},
- }, nil
+ }
+ return connection, nil
}
// pingNewConnection pings to make sure the RESTFUL service is up
-// and running. it should only be used where initializing a connection
+// and running. it should only be used when initializing a connection
func pingNewConnection(ctx context.Context) error {
client, err := GetClient(ctx)
if err != nil {
return err
}
// the ping endpoint sits at / in this case
- response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil)
+ response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil, nil)
if err != nil {
return err
}
+
if response.StatusCode == http.StatusOK {
- return nil
+ versionHdr := response.Header.Get("Libpod-API-Version")
+ if versionHdr == "" {
+ logrus.Info("Service did not provide Libpod-API-Version Header")
+ return nil
+ }
+ versionSrv, err := semver.ParseTolerant(versionHdr)
+ if err != nil {
+ return err
+ }
+
+ switch APIVersion.Compare(versionSrv) {
+ case 1, 0:
+ // Server's job when client version is equal or older
+ return nil
+ case -1:
+ return errors.Errorf("server API version is too old. client %q server %q", APIVersion.String(), versionSrv.String())
+ }
}
return errors.Errorf("ping response was %q", response.StatusCode)
}
-func sshClient(_url *url.URL, identity string, secure bool) (*http.Client, error) {
+func sshClient(_url *url.URL, identity string, secure bool) (Connection, error) {
auth, err := publicKey(identity)
if err != nil {
- return nil, errors.Wrapf(err, "Failed to parse identity %s: %v\n", _url.String(), identity)
+ return Connection{}, errors.Wrapf(err, "Failed to parse identity %s: %v\n", _url.String(), identity)
}
callback := ssh.InsecureIgnoreHostKey()
@@ -188,30 +214,43 @@ func sshClient(_url *url.URL, identity string, secure bool) (*http.Client, error
},
)
if err != nil {
- return nil, errors.Wrapf(err, "Connection to bastion host (%s) failed.", _url.String())
+ return Connection{}, errors.Wrapf(err, "Connection to bastion host (%s) failed.", _url.String())
}
- return &http.Client{
+
+ connection := Connection{_url: _url}
+ connection.client = &http.Client{
Transport: &http.Transport{
- DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
- return bastion.Dial("unix", _url.Path)
+ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
+ conn, err := bastion.Dial("unix", _url.Path)
+ if c, ok := ctx.Value(clientKey).(*Connection); ok {
+ c.conn = &conn
+ }
+ return conn, err
},
- }}, nil
+ }}
+ return connection, nil
}
-func unixClient(_url *url.URL) (*http.Client, error) {
- return &http.Client{
+func unixClient(_url *url.URL) (Connection, error) {
+ connection := Connection{_url: _url}
+ connection.client = &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
d := net.Dialer{}
- return d.DialContext(ctx, "unix", _url.Path)
+ conn, err := d.DialContext(ctx, "unix", _url.Path)
+ if c, ok := ctx.Value(clientKey).(*Connection); ok {
+ c.conn = &conn
+ }
+ return conn, err
},
DisableCompression: true,
},
- }, nil
+ }
+ return connection, nil
}
// DoRequest assembles the http request and returns the response
-func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, pathValues ...string) (*APIResponse, error) {
+func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) {
var (
err error
response *http.Response
@@ -232,6 +271,10 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
if len(queryParams) > 0 {
req.URL.RawQuery = queryParams.Encode()
}
+ for key, val := range header {
+ req.Header.Set(key, val)
+ }
+ req = req.WithContext(context.WithValue(context.Background(), clientKey, c))
// Give the Do three chances in the case of a comm/service hiccup
for i := 0; i < 3; i++ {
response, err = c.client.Do(req) // nolint
@@ -243,6 +286,10 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
return &APIResponse{response, req}, err
}
+func (c *Connection) Write(b []byte) (int, error) {
+ return (*c.conn).Write(b)
+}
+
// FiltersToString converts our typical filter format of a
// map[string][]string to a query/html safe string.
func FiltersToString(filters map[string][]string) (string, error) {
@@ -295,8 +342,8 @@ func publicKey(path string) (ssh.AuthMethod, error) {
func hostKey(host string) ssh.PublicKey {
// parse OpenSSH known_hosts file
// ssh or use ssh-keyscan to get initial key
- known_hosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
- fd, err := os.Open(known_hosts)
+ knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
+ fd, err := os.Open(knownHosts)
if err != nil {
logrus.Error(err)
return nil
diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go
index 84924587b..f483a9297 100644
--- a/pkg/bindings/containers/checkpoint.go
+++ b/pkg/bindings/containers/checkpoint.go
@@ -34,7 +34,7 @@ func Checkpoint(ctx context.Context, nameOrId string, keep, leaveRunning, tcpEst
if export != nil {
params.Set("export", *export)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nameOrId)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrId)
if err != nil {
return nil, err
}
@@ -71,7 +71,7 @@ func Restore(ctx context.Context, nameOrId string, keep, tcpEstablished, ignoreR
if importArchive != nil {
params.Set("import", *importArchive)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nameOrId)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrId)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/commit.go b/pkg/bindings/containers/commit.go
index 12c25f842..780d42272 100644
--- a/pkg/bindings/containers/commit.go
+++ b/pkg/bindings/containers/commit.go
@@ -41,7 +41,7 @@ func Commit(ctx context.Context, nameOrId string, options CommitOptions) (handle
if options.Tag != nil {
params.Set("tag", *options.Tag)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params, nil)
if err != nil {
return id, err
}
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index e74a256c7..516f3d282 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -2,9 +2,14 @@ package containers
import (
"context"
+ "encoding/binary"
+ "fmt"
"io"
"net/http"
"net/url"
+ "os"
+ "os/signal"
+ "reflect"
"strconv"
"strings"
@@ -12,7 +17,14 @@ import (
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
+ sig "github.com/containers/libpod/pkg/signal"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+var (
+ ErrLostSync = errors.New("lost synchronization with multiplexed stream")
)
// List obtains a list of containers in local storage. All parameters to this method are optional.
@@ -49,7 +61,7 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int
}
params.Set("filters", filterString)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params, nil)
if err != nil {
return containers, err
}
@@ -74,7 +86,7 @@ func Prune(ctx context.Context, filters map[string][]string) (*entities.Containe
}
params.Set("filters", filterString)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params, nil)
if err != nil {
return nil, err
}
@@ -96,7 +108,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error {
if volumes != nil {
params.Set("vols", strconv.FormatBool(*volumes))
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID)
if err != nil {
return err
}
@@ -116,7 +128,7 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*define.InspectC
if size != nil {
params.Set("size", strconv.FormatBool(*size))
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -134,7 +146,7 @@ func Kill(ctx context.Context, nameOrID string, sig string) error {
}
params := url.Values{}
params.Set("signal", sig)
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID)
if err != nil {
return err
}
@@ -149,7 +161,7 @@ func Pause(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -168,7 +180,7 @@ func Restart(ctx context.Context, nameOrID string, timeout *int) error {
if timeout != nil {
params.Set("t", strconv.Itoa(*timeout))
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID)
if err != nil {
return err
}
@@ -187,7 +199,7 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error {
if detachKeys != nil {
params.Set("detachKeys", *detachKeys)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID)
if err != nil {
return err
}
@@ -209,7 +221,7 @@ func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string,
// flatten the slice into one string
params.Set("ps_args", strings.Join(descriptors, ","))
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -237,7 +249,7 @@ func Unpause(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -247,7 +259,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 *define.ContainerStatus) (int32, error) { //nolint
+func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatus) (int32, error) { // nolint
var exitCode int32
conn, err := bindings.GetClient(ctx)
if err != nil {
@@ -257,7 +269,7 @@ func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatu
if condition != nil {
params.Set("condition", condition.String())
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID)
if err != nil {
return exitCode, err
}
@@ -272,7 +284,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@@ -290,7 +302,7 @@ func Stop(ctx context.Context, nameOrID string, timeout *uint) error {
if timeout != nil {
params.Set("t", strconv.Itoa(int(*timeout)))
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID)
if err != nil {
return err
}
@@ -305,7 +317,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID)
if err != nil {
return err
}
@@ -324,7 +336,7 @@ func ContainerInit(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -333,3 +345,248 @@ func ContainerInit(ctx context.Context, nameOrID string) error {
}
return response.Process(nil)
}
+
+// Attach attaches to a running container
+func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stream *bool, stdin io.Reader, stdout io.Writer, stderr io.Writer, attachReady chan bool) error {
+ isSet := struct {
+ stdin bool
+ stdout bool
+ stderr bool
+ }{
+ stdin: !(stdin == nil || reflect.ValueOf(stdin).IsNil()),
+ stdout: !(stdout == nil || reflect.ValueOf(stdout).IsNil()),
+ stderr: !(stderr == nil || reflect.ValueOf(stderr).IsNil()),
+ }
+ // Ensure golang can determine that interfaces are "really" nil
+ if !isSet.stdin {
+ stdin = (io.Reader)(nil)
+ }
+ if !isSet.stdout {
+ stdout = (io.Writer)(nil)
+ }
+ if !isSet.stderr {
+ stderr = (io.Writer)(nil)
+ }
+
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+
+ // Do we need to wire in stdin?
+ ctnr, err := Inspect(ctx, nameOrId, bindings.PFalse)
+ if err != nil {
+ return err
+ }
+
+ params := url.Values{}
+ if detachKeys != nil {
+ params.Add("detachKeys", *detachKeys)
+ }
+ if logs != nil {
+ params.Add("logs", fmt.Sprintf("%t", *logs))
+ }
+ if stream != nil {
+ params.Add("stream", fmt.Sprintf("%t", *stream))
+ }
+ if isSet.stdin {
+ params.Add("stdin", "true")
+ }
+ if isSet.stdout {
+ params.Add("stdout", "true")
+ }
+ if isSet.stderr {
+ params.Add("stderr", "true")
+ }
+
+ // Unless all requirements are met, don't use "stdin" is a terminal
+ file, ok := stdin.(*os.File)
+ needTTY := ok && terminal.IsTerminal(int(file.Fd())) && ctnr.Config.Tty
+ if needTTY {
+ state, err := terminal.MakeRaw(int(file.Fd()))
+ if err != nil {
+ return err
+ }
+
+ logrus.SetFormatter(&rawFormatter{})
+
+ defer func() {
+ if err := terminal.Restore(int(file.Fd()), state); err != nil {
+ logrus.Errorf("unable to restore terminal: %q", err)
+ }
+ logrus.SetFormatter(&logrus.TextFormatter{})
+ }()
+
+ winChange := make(chan os.Signal, 1)
+ signal.Notify(winChange, sig.SIGWINCH)
+ winCtx, winCancel := context.WithCancel(ctx)
+ defer winCancel()
+
+ go func() {
+ // Prime the pump, we need one reset to ensure everything is ready
+ winChange <- sig.SIGWINCH
+ for {
+ select {
+ case <-winCtx.Done():
+ return
+ case <-winChange:
+ h, w, err := terminal.GetSize(int(file.Fd()))
+ if err != nil {
+ logrus.Warnf("failed to obtain TTY size: " + err.Error())
+ }
+
+ if err := ResizeContainerTTY(ctx, nameOrId, &h, &w); err != nil {
+ logrus.Warnf("failed to resize TTY: " + err.Error())
+ }
+ }
+ }
+ }()
+ }
+
+ response, err := conn.DoRequest(stdin, http.MethodPost, "/containers/%s/attach", params, nil, nameOrId)
+ if err != nil {
+ return err
+ }
+ if !(response.IsSuccess() || response.IsInformational()) {
+ return response.Process(nil)
+ }
+
+ // If we are attaching around a start, we need to "signal"
+ // back that we are in fact attached so that started does
+ // not execute before we can attach.
+ if attachReady != nil {
+ attachReady <- true
+ }
+
+ buffer := make([]byte, 1024)
+ if ctnr.Config.Tty {
+ if !isSet.stdout {
+ return fmt.Errorf("container %q requires stdout to be set", ctnr.ID)
+ }
+ // If not multiplex'ed, read from server and write to stdout
+ _, err := io.Copy(stdout, response.Body)
+ if err != nil {
+ return err
+ }
+ } else {
+ for {
+ // Read multiplexed channels and write to appropriate stream
+ fd, l, err := DemuxHeader(response.Body, buffer)
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ return nil
+ }
+ return err
+ }
+ frame, err := DemuxFrame(response.Body, buffer, l)
+ if err != nil {
+ return err
+ }
+
+ switch {
+ case fd == 0 && isSet.stdout:
+ _, err := stdout.Write(frame[0:l])
+ if err != nil {
+ return err
+ }
+ case fd == 1 && isSet.stdout:
+ _, err := stdout.Write(frame[0:l])
+ if err != nil {
+ return err
+ }
+ case fd == 2 && isSet.stderr:
+ _, err := stderr.Write(frame[0:l])
+ if err != nil {
+ return err
+ }
+ case fd == 3:
+ return fmt.Errorf("error from service from stream: %s", frame)
+ default:
+ return fmt.Errorf("unrecognized channel in header: %d, 0-3 supported", fd)
+ }
+ }
+ }
+ return nil
+}
+
+// DemuxHeader reads header for stream from server multiplexed stdin/stdout/stderr/2nd error channel
+func DemuxHeader(r io.Reader, buffer []byte) (fd, sz int, err error) {
+ n, err := io.ReadFull(r, buffer[0:8])
+ if err != nil {
+ return
+ }
+ if n < 8 {
+ err = io.ErrUnexpectedEOF
+ return
+ }
+
+ fd = int(buffer[0])
+ if fd < 0 || fd > 3 {
+ err = errors.Wrapf(ErrLostSync, fmt.Sprintf(`channel "%d" found, 0-3 supported`, fd))
+ return
+ }
+
+ sz = int(binary.BigEndian.Uint32(buffer[4:8]))
+ return
+}
+
+// DemuxFrame reads contents for frame from server multiplexed stdin/stdout/stderr/2nd error channel
+func DemuxFrame(r io.Reader, buffer []byte, length int) (frame []byte, err error) {
+ if len(buffer) < length {
+ buffer = append(buffer, make([]byte, length-len(buffer)+1)...)
+ }
+
+ n, err := io.ReadFull(r, buffer[0:length])
+ if err != nil {
+ return nil, nil
+ }
+ if n < length {
+ err = io.ErrUnexpectedEOF
+ return
+ }
+
+ return buffer[0:length], nil
+}
+
+// ResizeContainerTTY sets container's TTY height and width in characters
+func ResizeContainerTTY(ctx context.Context, nameOrId string, height *int, width *int) error {
+ return resizeTTY(ctx, bindings.JoinURL("containers", nameOrId, "resize"), height, width)
+}
+
+// ResizeExecTTY sets session's TTY height and width in characters
+func ResizeExecTTY(ctx context.Context, nameOrId string, height *int, width *int) error {
+ return resizeTTY(ctx, bindings.JoinURL("exec", nameOrId, "resize"), height, width)
+}
+
+// resizeTTY set size of TTY of container
+func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+
+ params := url.Values{}
+ if height != nil {
+ params.Set("h", strconv.Itoa(*height))
+ }
+ if width != nil {
+ params.Set("w", strconv.Itoa(*width))
+ }
+ rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params, nil)
+ if err != nil {
+ return err
+ }
+ return rsp.Process(nil)
+}
+
+type rawFormatter struct {
+ logrus.TextFormatter
+}
+
+func (f *rawFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ buffer, err := f.TextFormatter.Format(entry)
+ if err != nil {
+ return buffer, err
+ }
+ return append(buffer, '\r'), nil
+}
diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go
index 21355f24b..4603b8653 100644
--- a/pkg/bindings/containers/create.go
+++ b/pkg/bindings/containers/create.go
@@ -22,7 +22,7 @@ func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator) (entities.Con
return ccr, err
}
stringReader := strings.NewReader(specgenString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil, nil)
if err != nil {
return ccr, err
}
diff --git a/pkg/bindings/containers/diff.go b/pkg/bindings/containers/diff.go
index 82070ca9a..06a828c30 100644
--- a/pkg/bindings/containers/diff.go
+++ b/pkg/bindings/containers/diff.go
@@ -15,7 +15,7 @@ func Diff(ctx context.Context, nameOrId string) ([]archive.Change, error) {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", nil, nameOrId)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", nil, nil, nameOrId)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go
index 48f9ed697..2aeeae1f8 100644
--- a/pkg/bindings/containers/exec.go
+++ b/pkg/bindings/containers/exec.go
@@ -34,7 +34,7 @@ func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreat
}
jsonReader := strings.NewReader(string(requestJSON))
- resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID)
+ resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID)
if err != nil {
return "", err
}
@@ -57,7 +57,7 @@ func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSess
logrus.Debugf("Inspecting session ID %q", sessionID)
- resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID)
+ resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go
index 2b783ac73..b726acf49 100644
--- a/pkg/bindings/containers/healthcheck.go
+++ b/pkg/bindings/containers/healthcheck.go
@@ -18,7 +18,7 @@ func RunHealthCheck(ctx context.Context, nameOrID string) (*define.HealthCheckRe
var (
status define.HealthCheckResults
)
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go
index b7ecb3c7e..bec4ebb3c 100644
--- a/pkg/bindings/containers/logs.go
+++ b/pkg/bindings/containers/logs.go
@@ -1,8 +1,9 @@
package containers
import (
+ "bytes"
"context"
- "encoding/binary"
+ "fmt"
"io"
"net/http"
"net/url"
@@ -45,72 +46,37 @@ func Logs(ctx context.Context, nameOrID string, opts LogOptions, stdoutChan, std
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)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID)
if err != nil {
return err
}
- // read 8 bytes
- // first byte determines stderr=2|stdout=1
- // bytes 4-7 len(msg) in uint32
+ buffer := make([]byte, 1024)
for {
- stream, msgSize, err := readHeader(response.Body)
+ fd, l, err := DemuxHeader(response.Body, buffer)
if err != nil {
- // In case the server side closes up shop because !follow
- if err == io.EOF {
- break
+ if errors.Is(err, io.EOF) {
+ return nil
}
- return errors.Wrap(err, "unable to read log header")
+ return err
}
- msg, err := readMsg(response.Body, msgSize)
+ frame, err := DemuxFrame(response.Body, buffer, l)
if err != nil {
- return errors.Wrap(err, "unable to read log message")
+ return err
}
- if stream == 1 {
- stdoutChan <- msg
- } else {
- stderrChan <- msg
- }
- }
- return nil
-}
+ frame = bytes.Replace(frame[0:l], []byte{13}, []byte{10}, -1)
-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
+ switch fd {
+ case 0:
+ stdoutChan <- string(frame)
+ case 1:
+ stdoutChan <- string(frame)
+ case 2:
+ stderrChan <- string(frame)
+ case 3:
+ return errors.New("error from service in stream: " + string(frame))
+ default:
+ return fmt.Errorf("unrecognized input header: %d", fd)
}
- 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/mount.go b/pkg/bindings/containers/mount.go
index e0627d9a3..2d553142f 100644
--- a/pkg/bindings/containers/mount.go
+++ b/pkg/bindings/containers/mount.go
@@ -17,7 +17,7 @@ func Mount(ctx context.Context, nameOrID string) (string, error) {
var (
path string
)
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID)
if err != nil {
return path, err
}
@@ -31,7 +31,7 @@ func Unmount(ctx context.Context, nameOrID string) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -45,7 +45,7 @@ func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) {
return nil, err
}
mounts := make(map[string]string)
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil, nil)
if err != nil {
return mounts, err
}
diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go
index 2916754b8..161b722f3 100644
--- a/pkg/bindings/generate/generate.go
+++ b/pkg/bindings/generate/generate.go
@@ -1,4 +1,32 @@
package generate
-func GenerateKube() {}
-func GenerateSystemd() {}
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ params.Set("service", strconv.FormatBool(options.Service))
+
+ response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+
+ if response.StatusCode == http.StatusOK {
+ return &entities.GenerateKubeReport{Reader: response.Body}, nil
+ }
+
+ // Unpack the error.
+ return nil, response.Process(nil)
+}
diff --git a/pkg/bindings/images/diff.go b/pkg/bindings/images/diff.go
index cfdd06a97..e2d344ea0 100644
--- a/pkg/bindings/images/diff.go
+++ b/pkg/bindings/images/diff.go
@@ -15,7 +15,7 @@ func Diff(ctx context.Context, nameOrId string) ([]archive.Change, error) {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nameOrId)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrId)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index 2305e0101..e0802a6e1 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -1,6 +1,7 @@
package images
import (
+ "bytes"
"context"
"fmt"
"io"
@@ -8,10 +9,14 @@ import (
"net/url"
"strconv"
+ "github.com/containers/buildah"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/auth"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/docker/go-units"
+ jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
)
@@ -22,7 +27,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@@ -48,7 +53,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit
}
params.Set("filters", strFilters)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params, nil)
if err != nil {
return imageSummary, err
}
@@ -67,15 +72,29 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image
params.Set("size", strconv.FormatBool(*size))
}
inspectedData := entities.ImageInspectReport{}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID)
if err != nil {
return &inspectedData, err
}
return &inspectedData, response.Process(&inspectedData)
}
-func Tree(ctx context.Context, nameOrId string) error {
- return bindings.ErrNotImplemented
+// Tree retrieves a "tree" based representation of the given image
+func Tree(ctx context.Context, nameOrId string, whatRequires *bool) (*entities.ImageTreeReport, error) {
+ var report entities.ImageTreeReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if whatRequires != nil {
+ params.Set("size", strconv.FormatBool(*whatRequires))
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrId)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
}
// History returns the parent layers of an image.
@@ -85,7 +104,7 @@ func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID)
if err != nil {
return history, err
}
@@ -102,43 +121,13 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe
if name != nil {
params.Set("reference", *name)
}
- response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params)
+ response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params, nil)
if err != nil {
return nil, err
}
return &report, response.Process(&report)
}
-// Remove deletes an image from local storage. The optional force parameter
-// will forcibly remove the image by removing all all containers, including
-// those that are Running, first.
-func Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
- var report handlers.LibpodImagesRemoveReport
- conn, err := bindings.GetClient(ctx)
- if err != nil {
- return nil, err
- }
- params := url.Values{}
- params.Set("all", strconv.FormatBool(opts.All))
- params.Set("force", strconv.FormatBool(opts.Force))
- for _, i := range images {
- params.Add("images", i)
- }
-
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/remove", params)
- if err != nil {
- return nil, err
- }
- if err := response.Process(&report); err != nil {
- return nil, err
- }
- var rmError error
- if report.Error != "" {
- rmError = errors.New(report.Error)
- }
- return &report.ImageRemoveReport, rmError
-}
-
// Export saves an image from local storage as a tarball or image archive. The optional format
// parameter is used to change the format of the output.
func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, compress *bool) error {
@@ -153,7 +142,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c
if compress != nil {
params.Set("compress", strconv.FormatBool(*compress))
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nil, nameOrID)
if err != nil {
return err
}
@@ -162,7 +151,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c
_, err = io.Copy(w, response.Body)
return err
}
- return nil
+ return response.Process(nil)
}
// Prune removes unused images from local storage. The optional filters can be used to further
@@ -186,7 +175,7 @@ func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]strin
}
params.Set("filters", stringFilter)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params, nil)
if err != nil {
return deleted, err
}
@@ -202,7 +191,7 @@ func Tag(ctx context.Context, nameOrID, tag, repo string) error {
params := url.Values{}
params.Set("tag", tag)
params.Set("repo", repo)
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID)
if err != nil {
return err
}
@@ -218,14 +207,115 @@ func Untag(ctx context.Context, nameOrID, tag, repo string) error {
params := url.Values{}
params.Set("tag", tag)
params.Set("repo", repo)
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID)
if err != nil {
return err
}
return response.Process(nil)
}
-func Build(nameOrId string) {}
+// Build creates an image using a containerfile reference
+func Build(ctx context.Context, containerFiles []string, options entities.BuildOptions, tarfile io.Reader) (*entities.BuildReport, error) {
+ var (
+ platform string
+ report entities.BuildReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ params.Set("dockerfile", containerFiles[0])
+ if t := options.Output; len(t) > 0 {
+ params.Set("t", t)
+ }
+ // TODO Remote, Quiet
+ if options.NoCache {
+ params.Set("nocache", "1")
+ }
+ // TODO cachefrom
+ if options.PullPolicy == buildah.PullAlways {
+ params.Set("pull", "1")
+ }
+ if options.RemoveIntermediateCtrs {
+ params.Set("rm", "1")
+ }
+ if options.ForceRmIntermediateCtrs {
+ params.Set("forcerm", "1")
+ }
+ if mem := options.CommonBuildOpts.Memory; mem > 0 {
+ params.Set("memory", strconv.Itoa(int(mem)))
+ }
+ if memSwap := options.CommonBuildOpts.MemorySwap; memSwap > 0 {
+ params.Set("memswap", strconv.Itoa(int(memSwap)))
+ }
+ if cpuShares := options.CommonBuildOpts.CPUShares; cpuShares > 0 {
+ params.Set("cpushares", strconv.Itoa(int(cpuShares)))
+ }
+ if cpuSetCpus := options.CommonBuildOpts.CPUSetCPUs; len(cpuSetCpus) > 0 {
+ params.Set("cpusetcpues", cpuSetCpus)
+ }
+ if cpuPeriod := options.CommonBuildOpts.CPUPeriod; cpuPeriod > 0 {
+ params.Set("cpuperiod", strconv.Itoa(int(cpuPeriod)))
+ }
+ if cpuQuota := options.CommonBuildOpts.CPUQuota; cpuQuota > 0 {
+ params.Set("cpuquota", strconv.Itoa(int(cpuQuota)))
+ }
+ if buildArgs := options.Args; len(buildArgs) > 0 {
+ bArgs, err := jsoniter.MarshalToString(buildArgs)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("buildargs", bArgs)
+ }
+ if shmSize := options.CommonBuildOpts.ShmSize; len(shmSize) > 0 {
+ shmBytes, err := units.RAMInBytes(shmSize)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("shmsize", strconv.Itoa(int(shmBytes)))
+ }
+ if options.Squash {
+ params.Set("squash", "1")
+ }
+ if labels := options.Labels; len(labels) > 0 {
+ l, err := jsoniter.MarshalToString(labels)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("labels", l)
+ }
+
+ // TODO network?
+ if OS := options.OS; len(OS) > 0 {
+ platform += OS
+ }
+ if arch := options.Architecture; len(arch) > 0 {
+ platform += "/" + arch
+ }
+ if len(platform) > 0 {
+ params.Set("platform", platform)
+ }
+ // TODO outputs?
+
+ response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params, nil)
+ if err != nil {
+ return nil, err
+ }
+ var streamReponse []byte
+ bb := bytes.NewBuffer(streamReponse)
+ if _, err = io.Copy(bb, response.Body); err != nil {
+ return nil, err
+ }
+ var s struct {
+ Stream string `json:"stream"`
+ }
+ if err := jsoniter.UnmarshalFromString(bb.String(), &s); err != nil {
+ return nil, err
+ }
+ fmt.Print(s.Stream)
+ return &report, nil
+}
// Imports adds the given image to the local image store. This can be done by file and the given reader
// or via the url parameter. Additional metadata can be associated with the image by using the changes and
@@ -252,7 +342,7 @@ func Import(ctx context.Context, changes []string, message, reference, u *string
if u != nil {
params.Set("url", *u)
}
- response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params)
+ response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params, nil)
if err != nil {
return nil, err
}
@@ -270,16 +360,22 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption
}
params := url.Values{}
params.Set("reference", rawImage)
- params.Set("credentials", options.Credentials)
params.Set("overrideArch", options.OverrideArch)
params.Set("overrideOS", options.OverrideOS)
- if options.TLSVerify != types.OptionalBoolUndefined {
- val := bool(options.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
params.Set("allTags", strconv.FormatBool(options.AllTags))
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params)
+ // TODO: have a global system context we can pass around (1st argument)
+ header, err := auth.Header(nil, options.Authfile, options.Username, options.Password)
+ if err != nil {
+ return nil, err
+ }
+
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params, header)
if err != nil {
return nil, err
}
@@ -307,17 +403,28 @@ func Push(ctx context.Context, source string, destination string, options entiti
if err != nil {
return err
}
+
+ // TODO: have a global system context we can pass around (1st argument)
+ header, err := auth.Header(nil, options.Authfile, options.Username, options.Password)
+ if err != nil {
+ return err
+ }
+
params := url.Values{}
- params.Set("credentials", options.Credentials)
params.Set("destination", destination)
- if options.TLSVerify != types.OptionalBoolUndefined {
- val := bool(options.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
path := fmt.Sprintf("/images/%s/push", source)
- _, err = conn.DoRequest(nil, http.MethodPost, path, params)
- return err
+ response, err := conn.DoRequest(nil, http.MethodPost, path, params, header)
+ if err != nil {
+ return err
+ }
+
+ return response.Process(err)
}
// Search is the binding for libpod's v2 endpoints for Search images.
@@ -333,12 +440,19 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions)
params.Set("filters", f)
}
- if opts.TLSVerify != types.OptionalBoolUndefined {
- val := bool(opts.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if opts.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(opts.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
+ }
+
+ // TODO: have a global system context we can pass around (1st argument)
+ header, err := auth.Header(nil, opts.Authfile, "", "")
+ if err != nil {
+ return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params, header)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go
new file mode 100644
index 000000000..c315bfce7
--- /dev/null
+++ b/pkg/bindings/images/rm.go
@@ -0,0 +1,65 @@
+package images
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/errorhandling"
+)
+
+// BachtRemove removes a batch of images from the local storage.
+func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) {
+ // FIXME - bindings tests are missing for this endpoint. Once the CI is
+ // re-enabled for bindings, we need to add them. At the time of writing,
+ // the tests don't compile.
+ var report handlers.LibpodImagesRemoveReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ params := url.Values{}
+ params.Set("all", strconv.FormatBool(opts.All))
+ params.Set("force", strconv.FormatBool(opts.Force))
+ for _, i := range images {
+ params.Add("images", i)
+ }
+
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params, nil)
+ if err != nil {
+ return nil, []error{err}
+ }
+ if err := response.Process(&report); err != nil {
+ return nil, []error{err}
+ }
+
+ return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors)
+}
+
+// Remove removes an image from the local storage. Use force to remove an
+// image, even if it's used by containers.
+func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRemoveReport, error) {
+ var report handlers.LibpodImagesRemoveReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ params := url.Values{}
+ params.Set("force", strconv.FormatBool(force))
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ if err := response.Process(&report); err != nil {
+ return nil, err
+ }
+
+ errs := errorhandling.StringsToErrors(report.Errors)
+ return &report.ImageRemoveReport, errorhandling.JoinErrors(errs)
+}
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
index a8d1e6ca3..e89624667 100644
--- a/pkg/bindings/manifests/manifests.go
+++ b/pkg/bindings/manifests/manifests.go
@@ -39,7 +39,7 @@ func Create(ctx context.Context, names, images []string, all *bool) (string, err
params.Add("image", i)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params, nil)
if err != nil {
return "", err
}
@@ -53,7 +53,7 @@ func Inspect(ctx context.Context, name string) (*manifest.Schema2List, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, name)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
if err != nil {
return nil, err
}
@@ -73,7 +73,7 @@ func Add(ctx context.Context, name string, options image.ManifestAddOpts) (strin
return "", err
}
stringReader := strings.NewReader(optionsString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, name)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name)
if err != nil {
return "", err
}
@@ -90,7 +90,7 @@ func Remove(ctx context.Context, name, digest string) (string, error) {
}
params := url.Values{}
params.Set("digest", digest)
- response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, name)
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, nil, name)
if err != nil {
return "", err
}
@@ -112,15 +112,37 @@ func Push(ctx context.Context, name string, destination *string, all *bool) (str
params := url.Values{}
params.Set("image", name)
if destination != nil {
- dest = name
+ dest = *destination
}
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)
+ _, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
if err != nil {
return "", err
}
- return idr.ID, response.Process(&idr)
+ return idr.ID, err
}
+
+// There is NO annotate endpoint. this binding could never work
+// Annotate updates the image configuration of a given manifest list
+//func Annotate(ctx context.Context, name, digest string, options image.ManifestAnnotateOpts) (string, error) {
+// var idr handlers.IDResponse
+// conn, err := bindings.GetClient(ctx)
+// if err != nil {
+// return "", err
+// }
+// params := url.Values{}
+// params.Set("digest", digest)
+// optionsString, err := jsoniter.MarshalToString(options)
+// if err != nil {
+// return "", err
+// }
+// stringReader := strings.NewReader(optionsString)
+// response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/annotate", params, name)
+// if err != nil {
+// return "", err
+// }
+// return idr.ID, response.Process(&idr)
+//}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
index c95b22953..34881b524 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -3,46 +3,82 @@ package network
import (
"context"
"net/http"
+ "net/url"
+ "strconv"
+ "strings"
- "github.com/containernetworking/cni/libcni"
"github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ jsoniter "github.com/json-iterator/go"
)
-func Create() {}
-func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, error) {
+// Create makes a new CNI network configuration
+func Create(ctx context.Context, options entities.NetworkCreateOptions, name *string) (*entities.NetworkCreateReport, error) {
+ var report entities.NetworkCreateReport
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
- n := make(map[string]interface{})
- response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nameOrID)
+ params := url.Values{}
+ if name != nil {
+ params.Set("name", *name)
+ }
+ networkConfig, err := jsoniter.MarshalToString(options)
if err != nil {
- return n, err
+ return nil, err
+ }
+ stringReader := strings.NewReader(networkConfig)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/create", params, nil)
+ if err != nil {
+ return nil, err
}
- return n, response.Process(&n)
+ return &report, response.Process(&report)
}
-func Remove(ctx context.Context, nameOrID string) error {
+// Inspect returns low level information about a CNI network configuration
+func Inspect(ctx context.Context, nameOrID string) ([]entities.NetworkInspectReport, error) {
+ var reports []entities.NetworkInspectReport
conn, err := bindings.GetClient(ctx)
if err != nil {
- return err
+ return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID)
if err != nil {
- return err
+ return nil, err
+ }
+ return reports, response.Process(&reports)
+}
+
+// Remove deletes a defined CNI network configuration by name. The optional force boolean
+// will remove all containers associated with the network when set to true. A slice
+// of NetworkRemoveReports are returned.
+func Remove(ctx context.Context, nameOrID string, force *bool) ([]*entities.NetworkRmReport, error) {
+ var reports []*entities.NetworkRmReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if force != nil {
+ params.Set("size", strconv.FormatBool(*force))
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID)
+ if err != nil {
+ return nil, err
}
- return response.Process(nil)
+ return reports, response.Process(&reports)
}
-func List(ctx context.Context) ([]*libcni.NetworkConfigList, error) {
+// List returns a summary of all CNI network configurations
+func List(ctx context.Context) ([]*entities.NetworkListReport, error) {
var (
- netList []*libcni.NetworkConfigList
+ netList []*entities.NetworkListReport
)
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil, nil)
if err != nil {
return netList, err
}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
index a6f03cad2..288cca454 100644
--- a/pkg/bindings/play/play.go
+++ b/pkg/bindings/play/play.go
@@ -1,7 +1,50 @@
package play
-import "github.com/containers/libpod/pkg/bindings"
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
-func PlayKube() error {
- return bindings.ErrNotImplemented
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/pkg/auth"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+ var report entities.PlayKubeReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ params := url.Values{}
+ params.Set("network", options.Network)
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ params.Set("tlsVerify", strconv.FormatBool(options.SkipTLSVerify == types.OptionalBoolTrue))
+ }
+
+ // TODO: have a global system context we can pass around (1st argument)
+ header, err := auth.Header(nil, options.Authfile, options.Username, options.Password)
+ if err != nil {
+ return nil, err
+ }
+
+ response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params, header)
+ if err != nil {
+ return nil, err
+ }
+ if err := response.Process(&report); err != nil {
+ return nil, err
+ }
+
+ return &report, nil
}
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
index b213c8c73..fb273fdf3 100644
--- a/pkg/bindings/pods/pods.go
+++ b/pkg/bindings/pods/pods.go
@@ -28,7 +28,7 @@ func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entit
return nil, err
}
stringReader := strings.NewReader(specgenString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil, nil)
if err != nil {
return nil, err
}
@@ -41,7 +41,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@@ -57,7 +57,7 @@ func Inspect(ctx context.Context, nameOrID string) (*entities.PodInspectReport,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -78,7 +78,7 @@ func Kill(ctx context.Context, nameOrID string, signal *string) (*entities.PodKi
if signal != nil {
params.Set("signal", *signal)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -92,7 +92,7 @@ func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, erro
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -107,7 +107,7 @@ func Prune(ctx context.Context) ([]*entities.PodPruneReport, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil, nil)
if err != nil {
return nil, err
}
@@ -132,7 +132,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.ListPod
}
params.Set("filters", stringFilter)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params, nil)
if err != nil {
return podsReports, err
}
@@ -146,7 +146,7 @@ func Restart(ctx context.Context, nameOrID string) (*entities.PodRestartReport,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -165,7 +165,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) (*entities.PodRmR
if force != nil {
params.Set("force", strconv.FormatBool(*force))
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -179,7 +179,7 @@ func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, erro
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -202,7 +202,7 @@ func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStop
if timeout != nil {
params.Set("t", strconv.Itoa(*timeout))
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -226,7 +226,7 @@ func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string,
// flatten the slice into one string
params.Set("ps_args", strings.Join(descriptors, ","))
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -254,7 +254,7 @@ func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -277,7 +277,7 @@ func Stats(ctx context.Context, namesOrIDs []string, options entities.PodStatsOp
params.Set("all", strconv.FormatBool(options.All))
var reports []*entities.PodStatsReport
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/system/info.go b/pkg/bindings/system/info.go
index 13e12645d..8ad704f84 100644
--- a/pkg/bindings/system/info.go
+++ b/pkg/bindings/system/info.go
@@ -15,7 +15,7 @@ func Info(ctx context.Context) (*define.Info, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go
index df6b529de..010762bef 100644
--- a/pkg/bindings/system/system.go
+++ b/pkg/bindings/system/system.go
@@ -3,11 +3,14 @@ package system
import (
"context"
"encoding/json"
+ "fmt"
"io"
"net/http"
"net/url"
"strconv"
+ "time"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
@@ -17,7 +20,7 @@ import (
// 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 (entities.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
+func Events(ctx context.Context, eventChan chan entities.Event, cancelChan chan bool, since, until *string, filters map[string][]string, stream *bool) error {
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
@@ -29,6 +32,9 @@ func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan cha
if until != nil {
params.Set("until", *until)
}
+ if stream != nil {
+ params.Set("stream", strconv.FormatBool(*stream))
+ }
if filters != nil {
filterString, err := bindings.FiltersToString(filters)
if err != nil {
@@ -36,7 +42,7 @@ func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan cha
}
params.Set("filters", filterString)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/events", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/events", params, nil)
if err != nil {
return err
}
@@ -47,18 +53,24 @@ func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan cha
logrus.Error(errors.Wrap(err, "unable to close event response body"))
}()
}
+
dec := json.NewDecoder(response.Body)
- for {
- e := entities.Event{}
- if err := dec.Decode(&e); err != nil {
- if err == io.EOF {
- break
- }
- return errors.Wrap(err, "unable to decode event response")
+ for err = (error)(nil); err == nil; {
+ var e = entities.Event{}
+ err = dec.Decode(&e)
+ if err == nil {
+ eventChan <- e
}
- eventChan <- e
}
- return nil
+ close(eventChan)
+ switch {
+ case err == nil:
+ return nil
+ case errors.Is(err, io.EOF):
+ return nil
+ default:
+ return errors.Wrap(err, "unable to decode event response")
+ }
}
// Prune removes all unused system data.
@@ -77,7 +89,57 @@ func Prune(ctx context.Context, all, volumes *bool) (*entities.SystemPruneReport
if volumes != nil {
params.Set("Volumes", strconv.FormatBool(*volumes))
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
+
+func Version(ctx context.Context) (*entities.SystemVersionReport, error) {
+ var report entities.SystemVersionReport
+ var component entities.ComponentVersion
+
+ version, err := define.GetVersion()
+ if err != nil {
+ return nil, err
+ }
+ report.Client = &version
+
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/version", nil, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ if err = response.Process(&component); err != nil {
+ return nil, err
+ }
+ f, _ := strconv.ParseFloat(component.APIVersion, 64)
+ b, _ := time.Parse(time.RFC3339, component.BuildTime)
+ report.Server = &define.Version{
+ APIVersion: int64(f),
+ Version: component.Version.Version,
+ GoVersion: component.GoVersion,
+ GitCommit: component.GitCommit,
+ Built: b.Unix(),
+ OsArch: fmt.Sprintf("%s/%s", component.Os, component.Arch),
+ }
+ return &report, err
+}
+
+// DiskUsage returns information about image, container, and volume disk
+// consumption
+func DiskUsage(ctx context.Context) (*entities.SystemDfReport, error) {
+ var report entities.SystemDfReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/system/df", nil, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go
new file mode 100644
index 000000000..6fb166828
--- /dev/null
+++ b/pkg/bindings/test/attach_test.go
@@ -0,0 +1,110 @@
+package test_bindings
+
+import (
+ "bytes"
+ "fmt"
+ "time"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/specgen"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman containers attach", 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).ShouldNot(HaveOccurred())
+ })
+
+ AfterEach(func() {
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("can run top in container", func() {
+ name := "TopAttachTest"
+ id, err := bt.RunTopContainer(&name, nil, nil)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ tickTock := time.NewTimer(2 * time.Second)
+ go func() {
+ <-tickTock.C
+ timeout := uint(5)
+ err := containers.Stop(bt.conn, id, &timeout)
+ if err != nil {
+ GinkgoWriter.Write([]byte(err.Error()))
+ }
+ }()
+
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+ go func() {
+ defer GinkgoRecover()
+
+ err := containers.Attach(bt.conn, id, nil, bindings.PTrue, bindings.PTrue, nil, stdout, stderr, nil)
+ Expect(err).ShouldNot(HaveOccurred())
+ }()
+
+ time.Sleep(5 * time.Second)
+
+ // First character/First line of top output
+ Expect(stdout.String()).Should(ContainSubstring("Mem: "))
+ })
+
+ It("can echo data via cat in container", func() {
+ s := specgen.NewSpecGenerator(alpine.name, false)
+ s.Name = "CatAttachTest"
+ s.Terminal = true
+ s.Command = []string{"/bin/cat"}
+ ctnr, err := containers.CreateWithSpec(bt.conn, s)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ err = containers.Start(bt.conn, ctnr.ID, nil)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ wait := define.ContainerStateRunning
+ _, err = containers.Wait(bt.conn, ctnr.ID, &wait)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ tickTock := time.NewTimer(2 * time.Second)
+ go func() {
+ <-tickTock.C
+ timeout := uint(5)
+ err := containers.Stop(bt.conn, ctnr.ID, &timeout)
+ if err != nil {
+ GinkgoWriter.Write([]byte(err.Error()))
+ }
+ }()
+
+ msg := "Hello, World"
+ stdin := &bytes.Buffer{}
+ stdin.WriteString(msg + "\n")
+
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+ go func() {
+ defer GinkgoRecover()
+
+ err := containers.Attach(bt.conn, ctnr.ID, nil, bindings.PFalse, bindings.PTrue, stdin, stdout, stderr, nil)
+ Expect(err).ShouldNot(HaveOccurred())
+ }()
+
+ time.Sleep(5 * time.Second)
+ // Tty==true so we get echo'ed stdin + expected output
+ Expect(stdout.String()).Should(Equal(fmt.Sprintf("%[1]s\r\n%[1]s\r\n", msg)))
+ Expect(stderr.String()).Should(BeEmpty())
+ })
+})
diff --git a/pkg/bindings/test/auth_test.go b/pkg/bindings/test/auth_test.go
new file mode 100644
index 000000000..fdb190551
--- /dev/null
+++ b/pkg/bindings/test/auth_test.go
@@ -0,0 +1,143 @@
+package test_bindings
+
+import (
+ "io/ioutil"
+ "os"
+ "time"
+
+ "github.com/containers/common/pkg/auth"
+ "github.com/containers/image/v5/types"
+ podmanRegistry "github.com/containers/libpod/hack/podman-registry-go"
+ "github.com/containers/libpod/pkg/bindings/images"
+ "github.com/containers/libpod/pkg/domain/entities"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman images", func() {
+ var (
+ registry *podmanRegistry.Registry
+ bt *bindingTest
+ s *gexec.Session
+ err error
+ )
+
+ BeforeEach(func() {
+ // Note: we need to start the registry **before** setting up
+ // the test. Otherwise, the registry is not reachable for
+ // currently unknown reasons.
+ registry, err = podmanRegistry.Start()
+ Expect(err).To(BeNil())
+
+ 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()
+ registry.Stop()
+ })
+
+ // Test using credentials.
+ It("tag + push + pull (with credentials)", func() {
+
+ imageRep := "localhost:" + registry.Port + "/test"
+ imageTag := "latest"
+ imageRef := imageRep + ":" + imageTag
+
+ // Tag the alpine image and verify it has worked.
+ err = images.Tag(bt.conn, alpine.shortName, imageTag, imageRep)
+ Expect(err).To(BeNil())
+ _, err = images.GetImage(bt.conn, imageRef, nil)
+ Expect(err).To(BeNil())
+
+ // Now push the image.
+ pushOpts := entities.ImagePushOptions{
+ Username: registry.User,
+ Password: registry.Password,
+ SkipTLSVerify: types.OptionalBoolTrue,
+ }
+ err = images.Push(bt.conn, imageRef, imageRef, pushOpts)
+ Expect(err).To(BeNil())
+
+ // Now pull the image.
+ pullOpts := entities.ImagePullOptions{
+ Username: registry.User,
+ Password: registry.Password,
+ SkipTLSVerify: types.OptionalBoolTrue,
+ }
+ _, err = images.Pull(bt.conn, imageRef, pullOpts)
+ Expect(err).To(BeNil())
+ })
+
+ // Test using authfile.
+ It("tag + push + pull + search (with authfile)", func() {
+
+ imageRep := "localhost:" + registry.Port + "/test"
+ imageTag := "latest"
+ imageRef := imageRep + ":" + imageTag
+
+ // Create a temporary authentication file.
+ tmpFile, err := ioutil.TempFile("", "auth.json.")
+ Expect(err).To(BeNil())
+ _, err = tmpFile.Write([]byte{'{', '}'})
+ Expect(err).To(BeNil())
+ err = tmpFile.Close()
+ Expect(err).To(BeNil())
+
+ authFilePath := tmpFile.Name()
+
+ // Now login to a) test the credentials and to b) store them in
+ // the authfile for later use.
+ sys := types.SystemContext{
+ AuthFilePath: authFilePath,
+ DockerInsecureSkipTLSVerify: types.OptionalBoolTrue,
+ }
+ loginOptions := auth.LoginOptions{
+ Username: registry.User,
+ Password: registry.Password,
+ AuthFile: authFilePath,
+ Stdin: os.Stdin,
+ Stdout: os.Stdout,
+ }
+ err = auth.Login(bt.conn, &sys, &loginOptions, []string{imageRep})
+ Expect(err).To(BeNil())
+
+ // Tag the alpine image and verify it has worked.
+ err = images.Tag(bt.conn, alpine.shortName, imageTag, imageRep)
+ Expect(err).To(BeNil())
+ _, err = images.GetImage(bt.conn, imageRef, nil)
+ Expect(err).To(BeNil())
+
+ // Now push the image.
+ pushOpts := entities.ImagePushOptions{
+ Authfile: authFilePath,
+ SkipTLSVerify: types.OptionalBoolTrue,
+ }
+ err = images.Push(bt.conn, imageRef, imageRef, pushOpts)
+ Expect(err).To(BeNil())
+
+ // Now pull the image.
+ pullOpts := entities.ImagePullOptions{
+ Authfile: authFilePath,
+ SkipTLSVerify: types.OptionalBoolTrue,
+ }
+ _, err = images.Pull(bt.conn, imageRef, pullOpts)
+ Expect(err).To(BeNil())
+
+ // Last, but not least, exercise search.
+ searchOptions := entities.ImageSearchOptions{
+ Authfile: authFilePath,
+ SkipTLSVerify: types.OptionalBoolTrue,
+ }
+ _, err = images.Search(bt.conn, imageRef, searchOptions)
+ Expect(err).To(BeNil())
+ })
+
+})
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index f33e42440..a86e6f2e3 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -191,7 +191,7 @@ func (b *bindingTest) restoreImageFromCache(i testImage) {
func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, podName *string) (string, error) {
s := specgen.NewSpecGenerator(alpine.name, false)
s.Terminal = false
- s.Command = []string{"top"}
+ s.Command = []string{"/usr/bin/top"}
if containerName != nil {
s.Name = *containerName
}
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index c79d89b73..3b94b10eb 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -56,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, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -70,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, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
@@ -84,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, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -100,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, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
// Pause by name
err = containers.Pause(bt.conn, name)
@@ -119,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, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -132,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, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
@@ -145,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, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
@@ -158,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, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, cid, nil)
Expect(err).To(BeNil())
@@ -171,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, &bindings.PFalse, 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, &bindings.PFalse, &bindings.PFalse)
+ err = containers.Remove(bt.conn, cid, bindings.PFalse, bindings.PFalse)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
@@ -192,18 +192,18 @@ var _ = Describe("Podman containers ", func() {
// Removing a paused container with force should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, 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, &bindings.PTrue, &bindings.PFalse)
+ 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, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, name)
Expect(err).To(BeNil())
@@ -216,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, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Pause(bt.conn, cid)
Expect(err).To(BeNil())
@@ -229,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, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
@@ -243,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, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, cid, nil)
Expect(err).To(BeNil())
@@ -302,6 +302,8 @@ var _ = Describe("Podman containers ", func() {
errChan = make(chan error)
go func() {
+ defer GinkgoRecover()
+
_, waitErr := containers.Wait(bt.conn, name, &running)
errChan <- waitErr
close(errChan)
@@ -324,7 +326,7 @@ var _ = Describe("Podman containers ", func() {
// a container that has no healthcheck should be a 409
var name = "top"
- bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ bt.RunTopContainer(&name, bindings.PFalse, nil)
_, err = containers.RunHealthCheck(bt.conn, name)
Expect(err).ToNot(BeNil())
code, _ = bindings.CheckResponseCode(err)
@@ -371,19 +373,19 @@ var _ = Describe("Podman containers ", func() {
_, err = containers.Wait(bt.conn, r.ID, nil)
Expect(err).To(BeNil())
- opts := containers.LogOptions{Stdout: &bindings.PTrue, Follow: &bindings.PTrue}
+ 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", "")
+ o = strings.TrimSpace(o)
_, err = time.Parse(time.RFC1123Z, o)
- Expect(err).To(BeNil())
+ Expect(err).ShouldNot(HaveOccurred())
})
It("podman top", func() {
var name = "top"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
// By name
@@ -421,7 +423,7 @@ var _ = Describe("Podman containers ", func() {
It("podman container exists in local storage by name", func() {
// Container existence check by name should work
var name = "top"
- _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
containerExists, err := containers.Exists(bt.conn, name)
Expect(err).To(BeNil())
@@ -431,7 +433,7 @@ var _ = Describe("Podman containers ", func() {
It("podman container exists in local storage by ID", func() {
// Container existence check by ID should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
containerExists, err := containers.Exists(bt.conn, cid)
Expect(err).To(BeNil())
@@ -441,7 +443,7 @@ var _ = Describe("Podman containers ", func() {
It("podman container exists in local storage by short ID", func() {
// Container existence check by short ID should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
containerExists, err := containers.Exists(bt.conn, cid[0:12])
Expect(err).To(BeNil())
@@ -459,7 +461,7 @@ var _ = Describe("Podman containers ", func() {
It("podman kill a running container by name with SIGINT", func() {
// Killing a running container should work
var name = "top"
- _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Kill(bt.conn, name, "SIGINT")
Expect(err).To(BeNil())
@@ -470,7 +472,7 @@ var _ = Describe("Podman containers ", func() {
It("podman kill a running container by ID with SIGTERM", func() {
// Killing a running container by ID should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Kill(bt.conn, cid, "SIGTERM")
Expect(err).To(BeNil())
@@ -481,7 +483,7 @@ var _ = Describe("Podman containers ", func() {
It("podman kill a running container by ID with SIGKILL", func() {
// Killing a running container by ID with TERM should work
var name = "top"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Kill(bt.conn, cid, "SIGKILL")
Expect(err).To(BeNil())
@@ -490,7 +492,7 @@ var _ = Describe("Podman containers ", func() {
It("podman kill a running container by bogus signal", func() {
//Killing a running container by bogus signal should fail
var name = "top"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Kill(bt.conn, cid, "foobar")
Expect(err).ToNot(BeNil())
@@ -503,9 +505,9 @@ var _ = Describe("Podman containers ", func() {
var name1 = "first"
var name2 = "second"
var latestContainers = 1
- _, err := bt.RunTopContainer(&name1, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name1, bindings.PFalse, nil)
Expect(err).To(BeNil())
- _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil)
+ _, err = bt.RunTopContainer(&name2, bindings.PFalse, nil)
Expect(err).To(BeNil())
containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil, nil)
Expect(err).To(BeNil())
@@ -531,10 +533,10 @@ var _ = Describe("Podman containers ", func() {
Expect(err).ToNot(BeNil())
})
- It("podman prune stoped containers", func() {
+ It("podman prune stopped containers", func() {
// Start and stop a container to enter in exited state.
var name = "top"
- _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
@@ -546,10 +548,10 @@ var _ = Describe("Podman containers ", func() {
Expect(len(pruneResponse.ID)).To(Equal(1))
})
- It("podman prune stoped containers with filters", func() {
+ It("podman prune stopped containers with filters", func() {
// Start and stop a container to enter in exited state.
var name = "top"
- _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
@@ -583,7 +585,7 @@ var _ = Describe("Podman containers ", func() {
It("podman prune running containers", func() {
// Start the container.
var name = "top"
- _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
// Check if the container is running.
@@ -596,4 +598,145 @@ var _ = Describe("Podman containers ", func() {
Expect(err).To(BeNil())
Expect(len(pruneResponse.ID)).To(Equal(0))
})
+
+ It("podman inspect bogus container", func() {
+ _, err := containers.Inspect(bt.conn, "foobar", nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+ It("podman inspect running container", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Inspecting running container should succeed
+ _, err = containers.Inspect(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ })
+
+ It("podman inspect stopped container", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ // Inspecting stopped container should succeed
+ _, err = containers.Inspect(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ })
+
+ It("podman inspect running container with size", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ _, err = containers.Inspect(bt.conn, name, bindings.PTrue)
+ Expect(err).To(BeNil())
+ })
+
+ It("podman inspect stopped container with size", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ // Inspecting stopped container with size should succeed
+ _, err = containers.Inspect(bt.conn, name, bindings.PTrue)
+ Expect(err).To(BeNil())
+ })
+
+ It("podman remove bogus container", func() {
+ err = containers.Remove(bt.conn, "foobar", nil, nil)
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+ It("podman remove running container by name", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, name, nil, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman remove running container by ID", func() {
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, cid, nil, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman forcibly remove running container by name", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, name, bindings.PTrue, nil)
+ Expect(err).To(BeNil())
+ //code, _ := bindings.CheckResponseCode(err)
+ //Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman forcibly remove running container by ID", func() {
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, cid, bindings.PTrue, nil)
+ Expect(err).To(BeNil())
+ //code, _ := bindings.CheckResponseCode(err)
+ //Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman remove running container and volume by name", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, name, nil, bindings.PTrue)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman remove running container and volume by ID", func() {
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, cid, nil, bindings.PTrue)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman forcibly remove running container and volume by name", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, name, bindings.PTrue, bindings.PTrue)
+ Expect(err).To(BeNil())
+ //code, _ := bindings.CheckResponseCode(err)
+ //Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman forcibly remove running container and volume by ID", func() {
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Removing running container should fail
+ err = containers.Remove(bt.conn, cid, bindings.PTrue, bindings.PTrue)
+ Expect(err).To(BeNil())
+ //code, _ := bindings.CheckResponseCode(err)
+ //Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
})
diff --git a/pkg/bindings/test/exec_test.go b/pkg/bindings/test/exec_test.go
index 1ef2197b6..53b2dcb4a 100644
--- a/pkg/bindings/test/exec_test.go
+++ b/pkg/bindings/test/exec_test.go
@@ -33,7 +33,7 @@ var _ = Describe("Podman containers exec", func() {
It("Podman exec create makes an exec session", func() {
name := "testCtr"
- cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
execConfig := new(handlers.ExecCreateConfig)
@@ -53,7 +53,7 @@ var _ = Describe("Podman containers exec", func() {
It("Podman exec create with bad command fails", func() {
name := "testCtr"
- _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err := bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
execConfig := new(handlers.ExecCreateConfig)
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
index 58210efd0..f2a1a51e5 100644
--- a/pkg/bindings/test/images_test.go
+++ b/pkg/bindings/test/images_test.go
@@ -76,7 +76,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, &bindings.PTrue)
+ data, err = images.GetImage(bt.conn, alpine.name, bindings.PTrue)
Expect(err).To(BeNil())
Expect(data.Size).To(BeNumerically(">", 0))
})
@@ -84,50 +84,54 @@ 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", &bindings.PFalse)
+ response, err := images.Remove(bt.conn, "foobar5000", false)
Expect(err).ToNot(BeNil())
+ Expect(response).To(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Remove an image by name, validate image is removed and error is nil
inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
- response, err := images.Remove(bt.conn, busybox.shortName, nil)
+ response, err = images.Remove(bt.conn, busybox.shortName, false)
Expect(err).To(BeNil())
- Expect(inspectData.ID).To(Equal(response[0]["Deleted"]))
+ code, _ = bindings.CheckResponseCode(err)
+
+ Expect(inspectData.ID).To(Equal(response.Deleted[0]))
inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Start a container with alpine image
var top string = "top"
- _, err = bt.RunTopContainer(&top, &bindings.PFalse, 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", &bindings.PFalse)
+ containerResponse, err := containers.Inspect(bt.conn, "top", nil)
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, &bindings.PFalse)
+ response, err = images.Remove(bt.conn, alpine.shortName, false)
code, _ = bindings.CheckResponseCode(err)
- Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ Expect(code).To(BeNumerically("==", http.StatusConflict))
// Removing the image "alpine" where force = true
- response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PTrue)
+ response, err = images.Remove(bt.conn, alpine.shortName, true)
Expect(err).To(BeNil())
-
- // Checking if both the images are gone as well as the container is deleted
- inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
+ // To be extra sure, check if the previously created container
+ // is gone as well.
+ _, err = containers.Inspect(bt.conn, "top", bindings.PFalse)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
- inspectData, err = images.GetImage(bt.conn, alpine.shortName, nil)
+ // Now make sure both images are gone.
+ inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
- _, err = containers.Inspect(bt.conn, "top", &bindings.PFalse)
+ inspectData, err = images.GetImage(bt.conn, alpine.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
})
@@ -178,13 +182,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, &bindings.PFalse, 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, &bindings.PFalse, filters)
+ _, err = images.List(bt.conn, bindings.PFalse, filters)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
@@ -209,7 +213,7 @@ var _ = Describe("Podman images", func() {
It("Load|Import Image", func() {
// load an image
- _, err := images.Remove(bt.conn, alpine.name, nil)
+ _, err := images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -219,7 +223,7 @@ var _ = Describe("Podman images", func() {
Expect(err).To(BeNil())
names, err := images.Load(bt.conn, f, nil)
Expect(err).To(BeNil())
- Expect(names.Name).To(Equal(alpine.name))
+ Expect(names.Names[0]).To(Equal(alpine.name))
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
@@ -227,7 +231,7 @@ var _ = Describe("Podman images", func() {
// load with a repo name
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, nil)
+ _, err = images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -235,7 +239,7 @@ var _ = Describe("Podman images", func() {
newName := "quay.io/newname:fizzle"
names, err = images.Load(bt.conn, f, &newName)
Expect(err).To(BeNil())
- Expect(names.Name).To(Equal(alpine.name))
+ Expect(names.Names[0]).To(Equal(alpine.name))
exists, err = images.Exists(bt.conn, newName)
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
@@ -243,7 +247,7 @@ var _ = Describe("Podman images", func() {
// load with a bad repo name should trigger a 500
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, nil)
+ _, err = images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -271,7 +275,7 @@ var _ = Describe("Podman images", func() {
It("Import Image", func() {
// load an image
- _, err = images.Remove(bt.conn, alpine.name, nil)
+ _, err = images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
index 23c3d8194..71d626b7b 100644
--- a/pkg/bindings/test/manifests_test.go
+++ b/pkg/bindings/test/manifests_test.go
@@ -47,7 +47,7 @@ var _ = Describe("Podman containers ", func() {
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
- _, err = images.Remove(bt.conn, id, nil)
+ _, err = images.Remove(bt.conn, id, false)
Expect(err).To(BeNil())
// create manifest list with images
@@ -118,6 +118,28 @@ var _ = Describe("Podman containers ", func() {
Expect(len(data.Manifests)).To(BeZero())
})
+ // There is NO annotate endpoint, this could never work.:w
+
+ //It("annotate manifest", func() {
+ // id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ // Expect(err).To(BeNil())
+ // opts := image.ManifestAddOpts{Images: []string{"docker.io/library/alpine:latest"}}
+ //
+ // _, err = manifests.Add(bt.conn, id, opts)
+ // Expect(err).To(BeNil())
+ // data, err := manifests.Inspect(bt.conn, id)
+ // Expect(err).To(BeNil())
+ // Expect(len(data.Manifests)).To(BeNumerically("==", 1))
+ // digest := data.Manifests[0].Digest.String()
+ // annoOpts := image.ManifestAnnotateOpts{OS: "foo"}
+ // _, err = manifests.Annotate(bt.conn, id, digest, annoOpts)
+ // Expect(err).To(BeNil())
+ // list, err := manifests.Inspect(bt.conn, id)
+ // Expect(err).To(BeNil())
+ // Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+ // Expect(list.Manifests[0].Platform.OS).To(Equal("foo"))
+ //})
+
It("push manifest", func() {
Skip("TODO")
})
diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go
index 8a0b9c7a6..d8e2a5ef7 100644
--- a/pkg/bindings/test/pods_test.go
+++ b/pkg/bindings/test/pods_test.go
@@ -57,8 +57,13 @@ var _ = Describe("Podman pods", func() {
podSummary, err := pods.List(bt.conn, nil)
Expect(err).To(BeNil())
Expect(len(podSummary)).To(Equal(1))
+
+ // Start the pod
+ _, err = pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+
// Adding an alpine container to the existing pod
- _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
+ _, err = bt.RunTopContainer(nil, bindings.PTrue, &newpod)
Expect(err).To(BeNil())
podSummary, err = pods.List(bt.conn, nil)
// Verify no errors.
@@ -83,7 +88,12 @@ var _ = Describe("Podman pods", func() {
It("List pods with filters", func() {
newpod2 := "newpod2"
bt.Podcreate(&newpod2)
- _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
+
+ // Start the pod
+ _, err = pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+
+ _, err = bt.RunTopContainer(nil, bindings.PTrue, &newpod)
Expect(err).To(BeNil())
// Expected err with invalid filter params
@@ -164,7 +174,7 @@ var _ = Describe("Podman pods", func() {
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Adding an alpine container to the existing pod
- _, err = bt.RunTopContainer(nil, &bindings.PTrue, &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
index 87e6d56dc..dd3778754 100644
--- a/pkg/bindings/test/system_test.go
+++ b/pkg/bindings/test/system_test.go
@@ -3,7 +3,6 @@ package test_bindings
import (
"time"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/bindings/containers"
"github.com/containers/libpod/pkg/bindings/pods"
@@ -39,8 +38,8 @@ var _ = Describe("Podman system", func() {
})
It("podman events", func() {
- eChan := make(chan handlers.Event, 1)
- var messages []handlers.Event
+ eChan := make(chan entities.Event, 1)
+ var messages []entities.Event
cancelChan := make(chan bool, 1)
go func() {
for e := range eChan {
@@ -48,13 +47,13 @@ var _ = Describe("Podman system", func() {
}
}()
go func() {
- system.Events(bt.conn, eChan, cancelChan, nil, nil, nil)
+ system.Events(bt.conn, eChan, cancelChan, nil, nil, nil, bindings.PFalse)
}()
_, err := bt.RunTopContainer(nil, nil, nil)
Expect(err).To(BeNil())
cancelChan <- true
- Expect(len(messages)).To(BeNumerically("==", 3))
+ Expect(len(messages)).To(BeNumerically("==", 5))
})
It("podman system prune - pod,container stopped", func() {
@@ -65,12 +64,12 @@ var _ = Describe("Podman system", func() {
Expect(err).To(BeNil())
// Start and stop a container to enter in exited state.
var name = "top"
- _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err = bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
- systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse)
+ systemPruneResponse, err := system.Prune(bt.conn, bindings.PTrue, bindings.PFalse)
Expect(err).To(BeNil())
Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1))
Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
@@ -90,21 +89,21 @@ var _ = Describe("Podman system", func() {
// Start and stop a container to enter in exited state.
var name = "top"
- _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err = bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
// Start container and leave in running
var name2 = "top2"
- _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil)
+ _, err = bt.RunTopContainer(&name2, bindings.PFalse, nil)
Expect(err).To(BeNil())
// Adding an unused volume
_, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
- systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse)
+ systemPruneResponse, err := system.Prune(bt.conn, bindings.PTrue, bindings.PFalse)
Expect(err).To(BeNil())
Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1))
Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
@@ -124,21 +123,21 @@ var _ = Describe("Podman system", func() {
// Start and stop a container to enter in exited state.
var name = "top"
- _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err = bt.RunTopContainer(&name, bindings.PFalse, nil)
Expect(err).To(BeNil())
err = containers.Stop(bt.conn, name, nil)
Expect(err).To(BeNil())
// Start second container and leave in running
var name2 = "top2"
- _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil)
+ _, err = bt.RunTopContainer(&name2, bindings.PFalse, nil)
Expect(err).To(BeNil())
// Adding an unused volume should work
_, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{})
Expect(err).To(BeNil())
- systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PTrue)
+ systemPruneResponse, err := system.Prune(bt.conn, bindings.PTrue, bindings.PTrue)
Expect(err).To(BeNil())
Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(0))
Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
diff --git a/pkg/bindings/test/test_suite_test.go b/pkg/bindings/test/test_suite_test.go
index dc2b49b88..d2c2c7838 100644
--- a/pkg/bindings/test/test_suite_test.go
+++ b/pkg/bindings/test/test_suite_test.go
@@ -5,9 +5,14 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ "github.com/sirupsen/logrus"
)
func TestTest(t *testing.T) {
+ if testing.Verbose() {
+ logrus.SetLevel(logrus.DebugLevel)
+ }
+
RegisterFailHandler(Fail)
RunSpecs(t, "Test Suite")
}
diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go
index 59fe48f22..839a4c575 100644
--- a/pkg/bindings/test/volumes_test.go
+++ b/pkg/bindings/test/volumes_test.go
@@ -105,7 +105,7 @@ var _ = Describe("Podman volumes", func() {
zero := uint(0)
err = containers.Stop(connText, "vtest", &zero)
Expect(err).To(BeNil())
- err = volumes.Remove(connText, vol.Name, &bindings.PTrue)
+ err = volumes.Remove(connText, vol.Name, bindings.PTrue)
Expect(err).To(BeNil())
})
diff --git a/pkg/bindings/version.go b/pkg/bindings/version.go
deleted file mode 100644
index c833a644c..000000000
--- a/pkg/bindings/version.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package bindings
-
-func (c Connection) Version() {}
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
index cef9246cb..ebe19794a 100644
--- a/pkg/bindings/volumes/volumes.go
+++ b/pkg/bindings/volumes/volumes.go
@@ -26,7 +26,7 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions) (*entities
return nil, err
}
stringReader := strings.NewReader(createString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil, nil)
if err != nil {
return nil, err
}
@@ -42,7 +42,7 @@ func Inspect(ctx context.Context, nameOrID string) (*entities.VolumeConfigRespon
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID)
if err != nil {
return &inspect, err
}
@@ -67,7 +67,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeL
}
params.Set("filters", strFilters)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params, nil)
if err != nil {
return vols, err
}
@@ -83,7 +83,7 @@ func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil, nil)
if err != nil {
return nil, err
}
@@ -101,7 +101,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error {
if force != nil {
params.Set("force", strconv.FormatBool(*force))
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID)
if err != nil {
return err
}
diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go
index d51905f4b..3b56f944f 100644
--- a/pkg/cgroups/cgroups.go
+++ b/pkg/cgroups/cgroups.go
@@ -517,6 +517,10 @@ func (c *CgroupControl) AddPid(pid int) error {
}
for _, n := range names {
+ // If we aren't using cgroup2, we won't write correctly to unified hierarchy
+ if !c.cgroup2 && n == "unified" {
+ continue
+ }
p := filepath.Join(c.getCgroupv1Path(n), "tasks")
if err := ioutil.WriteFile(p, pidString, 0644); err != nil {
return errors.Wrapf(err, "write %s", p)
diff --git a/pkg/domain/entities/auto-update.go b/pkg/domain/entities/auto-update.go
new file mode 100644
index 000000000..c51158816
--- /dev/null
+++ b/pkg/domain/entities/auto-update.go
@@ -0,0 +1,13 @@
+package entities
+
+// AutoUpdateOptions are the options for running auto-update.
+type AutoUpdateOptions struct {
+ // Authfile to use when contacting registries.
+ Authfile string
+}
+
+// AutoUpdateReport contains the results from running auto-update.
+type AutoUpdateReport struct {
+ // Units - the restarted systemd units during auto-update.
+ Units []string
+}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index e58258b75..8d85a9b23 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -6,11 +6,49 @@ import (
"os"
"time"
+ "github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/specgen"
"github.com/cri-o/ocicni/pkg/ocicni"
)
+// ContainerRunlabelOptions are the options to execute container-runlabel.
+type ContainerRunlabelOptions struct {
+ // Authfile - path to an authentication file.
+ Authfile string
+ // CertDir - path to a directory containing TLS certifications and
+ // keys.
+ CertDir string
+ // Credentials - `user:password` to use when pulling an image.
+ Credentials string
+ // Display - do not execute but print the command.
+ Display bool
+ // Replace - replace an existing container with a new one from the
+ // image.
+ Replace bool
+ // Name - use this name when executing the runlabel container.
+ Name string
+ // Optional1 - fist optional parameter for install.
+ Optional1 string
+ // Optional2 - second optional parameter for install.
+ Optional2 string
+ // Optional3 - third optional parameter for install.
+ Optional3 string
+ // Pull - pull the specified image if it's not in the local storage.
+ Pull bool
+ // Quiet - suppress output when pulling images.
+ Quiet bool
+ // SignaturePolicy - path to a signature-policy file.
+ SignaturePolicy string
+ // SkipTLSVerify - skip HTTPS and certificate verifications when
+ // contacting registries.
+ SkipTLSVerify types.OptionalBool
+}
+
+// ContainerRunlabelReport contains the results from executing container-runlabel.
+type ContainerRunlabelReport struct {
+}
+
type WaitOptions struct {
Condition define.ContainerStatus
Interval time.Duration
@@ -132,7 +170,7 @@ type CheckpointOptions struct {
IgnoreRootFS bool
Keep bool
Latest bool
- LeaveRuninng bool
+ LeaveRunning bool
TCPEstablished bool
}
@@ -204,7 +242,6 @@ type ExecOptions struct {
Latest bool
PreserveFDs uint
Privileged bool
- Streams define.AttachStreams
Tty bool
User string
WorkDir string
@@ -227,6 +264,7 @@ type ContainerStartOptions struct {
// containers from the cli
type ContainerStartReport struct {
Id string
+ RawInput string
Err error
ExitCode int
}
@@ -272,6 +310,7 @@ type ContainerRunReport struct {
// cleanup command
type ContainerCleanupOptions struct {
All bool
+ Exec string
Latest bool
Remove bool
RemoveImage bool
@@ -366,3 +405,14 @@ type ContainerCpOptions struct {
// ContainerCpReport describes the output from a cp operation
type ContainerCpReport struct {
}
+
+// ContainerStatsOptions describes input options for getting
+// stats on containers
+type ContainerStatsOptions struct {
+ All bool
+ Format string
+ Latest bool
+ NoReset bool
+ NoStream bool
+ StatChan chan []*define.ContainerStats
+}
diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go
index f45218d14..db58befa5 100644
--- a/pkg/domain/entities/engine.go
+++ b/pkg/domain/entities/engine.go
@@ -12,9 +12,18 @@ import (
// EngineMode is the connection type podman is using to access libpod
type EngineMode string
+// EngineSetup calls out whether a "normal" or specialized engine should be created
+type EngineSetup string
+
const (
ABIMode = EngineMode("abi")
TunnelMode = EngineMode("tunnel")
+
+ MigrateMode = EngineSetup("migrate")
+ NoFDsMode = EngineSetup("disablefds")
+ NormalMode = EngineSetup("normal")
+ RenumberMode = EngineSetup("renumber")
+ ResetMode = EngineSetup("reset")
)
// Convert EngineMode to String
@@ -28,19 +37,20 @@ type PodmanConfig struct {
*config.Config
*pflag.FlagSet
- CGroupUsage string // rootless code determines Usage message
- ConmonPath string // --conmon flag will set Engine.ConmonPath
- CpuProfile string // Hidden: Should CPU profile be taken
- EngineMode EngineMode // ABI or Tunneling mode
- Identities []string // ssh identities for connecting to server
- MaxWorks int // maximum number of parallel threads
- RuntimePath string // --runtime flag will set Engine.RuntimePath
- SpanCloser io.Closer // Close() for tracing object
- SpanCtx context.Context // context to use when tracing
- Span opentracing.Span // tracing object
- Syslog bool // write to StdOut and Syslog, not supported when tunneling
- Trace bool // Hidden: Trace execution
- Uri string // URI to API Service
+ CGroupUsage string // rootless code determines Usage message
+ ConmonPath string // --conmon flag will set Engine.ConmonPath
+ CpuProfile string // Hidden: Should CPU profile be taken
+ EngineMode EngineMode // ABI or Tunneling mode
+ Identities []string // ssh identities for connecting to server
+ MaxWorks int // maximum number of parallel threads
+ RegistriesConf string // allows for specifying a custom registries.conf
+ RuntimePath string // --runtime flag will set Engine.RuntimePath
+ SpanCloser io.Closer // Close() for tracing object
+ SpanCtx context.Context // context to use when tracing
+ Span opentracing.Span // tracing object
+ Syslog bool // write to StdOut and Syslog, not supported when tunneling
+ Trace bool // Hidden: Trace execution
+ Uri string // URI to API Service
Runroot string
StorageDriver string
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index eebf4c033..3d5161745 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -10,6 +10,7 @@ import (
)
type ContainerEngine interface {
+ AutoUpdate(ctx context.Context, options AutoUpdateOptions) (*AutoUpdateReport, []error)
Config(ctx context.Context) (*config.Config, error)
ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
@@ -18,7 +19,8 @@ type ContainerEngine interface {
ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error)
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
- ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error)
+ ContainerExec(ctx context.Context, nameOrId string, options ExecOptions, streams define.AttachStreams) (int, error)
+ ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, error)
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error
ContainerInit(ctx context.Context, namesOrIds []string, options ContainerInitOptions) ([]*ContainerInitReport, error)
@@ -34,7 +36,9 @@ type ContainerEngine interface {
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error)
+ ContainerRunlabel(ctx context.Context, label string, image string, args []string, opts ContainerRunlabelOptions) error
ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error)
+ ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error)
ContainerUnmount(ctx context.Context, nameOrIds []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error)
@@ -42,8 +46,15 @@ type ContainerEngine interface {
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
+ GenerateKube(ctx context.Context, nameOrID string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
+ SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
+ NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (*NetworkCreateReport, error)
+ NetworkInspect(ctx context.Context, namesOrIds []string, options NetworkInspectOptions) ([]NetworkInspectReport, error)
+ NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error)
+ NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
+ PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
PodExists(ctx context.Context, nameOrId string) (*BoolReport, error)
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
@@ -60,7 +71,10 @@ type ContainerEngine interface {
PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
SetupRootless(ctx context.Context, cmd *cobra.Command) error
Shutdown(ctx context.Context)
+ SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error)
+ Unshare(ctx context.Context, args []string) error
VarlinkService(ctx context.Context, opts ServiceOptions) error
+ Version(ctx context.Context) (*SystemVersionReport, error)
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error)
VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error)
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index 46a96ca20..7d7099838 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -19,9 +19,11 @@ type ImageEngine interface {
Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
- Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, error)
+ Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, []error)
Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
+ SetTrust(ctx context.Context, args []string, options SetTrustOptions) error
+ ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error)
Shutdown(ctx context.Context)
Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
Tree(ctx context.Context, nameOrId string, options ImageTreeOptions) (*ImageTreeReport, error)
@@ -29,4 +31,8 @@ type ImageEngine interface {
ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error)
ManifestInspect(ctx context.Context, name string) ([]byte, error)
ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error)
+ ManifestAnnotate(ctx context.Context, names []string, opts ManifestAnnotateOptions) (string, error)
+ ManifestRemove(ctx context.Context, names []string) (string, error)
+ ManifestPush(ctx context.Context, names []string, manifestPushOpts ManifestPushOptions) error
+ Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error)
}
diff --git a/pkg/domain/entities/engine_system.go b/pkg/domain/entities/engine_system.go
new file mode 100644
index 000000000..a0ecfe9ea
--- /dev/null
+++ b/pkg/domain/entities/engine_system.go
@@ -0,0 +1,14 @@
+package entities
+
+import (
+ "context"
+
+ "github.com/spf13/pflag"
+)
+
+type SystemEngine interface {
+ Renumber(ctx context.Context, flags *pflag.FlagSet, config *PodmanConfig) error
+ Migrate(ctx context.Context, flags *pflag.FlagSet, config *PodmanConfig, options SystemMigrateOptions) error
+ Reset(ctx context.Context) error
+ Shutdown(ctx context.Context)
+}
diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go
index 6d65b52f8..68a42d897 100644
--- a/pkg/domain/entities/generate.go
+++ b/pkg/domain/entities/generate.go
@@ -1,5 +1,7 @@
package entities
+import "io"
+
// GenerateSystemdOptions control the generation of systemd unit files.
type GenerateSystemdOptions struct {
// Files - generate files instead of printing to stdout.
@@ -12,6 +14,12 @@ type GenerateSystemdOptions struct {
RestartPolicy string
// StopTimeout - time when stopping the container.
StopTimeout *uint
+ // ContainerPrefix - systemd unit name prefix for containers
+ ContainerPrefix string
+ // PodPrefix - systemd unit name prefix for pods
+ PodPrefix string
+ // Separator - systemd unit name seperator between name/id and prefix
+ Separator string
}
// GenerateSystemdReport
@@ -20,3 +28,15 @@ type GenerateSystemdReport struct {
// entire content.
Output string
}
+
+// GenerateKubeOptions control the generation of Kubernetes YAML files.
+type GenerateKubeOptions struct {
+ // Service - generate YAML for a Kubernetes _service_ object.
+ Service bool
+}
+
+// GenerateKubeReport
+type GenerateKubeReport struct {
+ // Reader - the io.Reader to reader the generated YAML file.
+ Reader io.Reader
+}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 707937a44..19a2c87f5 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -1,12 +1,12 @@
package entities
import (
- "net/url"
"time"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/pkg/inspect"
+ "github.com/containers/libpod/pkg/trust"
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/opencontainers/go-digest"
@@ -50,7 +50,7 @@ func (i *Image) Id() string {
}
type ImageSummary struct {
- ID string
+ ID string `json:"Id"`
ParentId string `json:",omitempty"`
RepoTags []string `json:",omitempty"`
Created time.Time `json:",omitempty"`
@@ -128,9 +128,10 @@ type ImagePullOptions struct {
// CertDir is the path to certificate directories. Ignored for remote
// calls.
CertDir string
- // Credentials for authenticating against the registry in the format
- // USERNAME:PASSWORD.
- Credentials string
+ // Username for authenticating against the registry.
+ Username string
+ // Password for authenticating against the registry.
+ Password string
// OverrideArch will overwrite the local architecture for image pulls.
OverrideArch string
// OverrideOS will overwrite the local operating system (OS) for image
@@ -141,8 +142,8 @@ type ImagePullOptions struct {
Quiet bool
// SignaturePolicy to use when pulling. Ignored for remote calls.
SignaturePolicy string
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImagePullReport is the response from pulling one or more images.
@@ -162,9 +163,10 @@ type ImagePushOptions struct {
// transport. Default is same compression type as source. Ignored for remote
// calls.
Compress bool
- // Credentials for authenticating against the registry in the format
- // USERNAME:PASSWORD.
- Credentials string
+ // Username for authenticating against the registry.
+ Username string
+ // Password for authenticating against the registry.
+ Password string
// DigestFile, after copying the image, write the digest of the resulting
// image to the file. Ignored for remote calls.
DigestFile string
@@ -183,8 +185,8 @@ type ImagePushOptions struct {
// SignBy adds a signature at the destination using the specified key.
// Ignored for remote calls.
SignBy string
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImageSearchOptions are the arguments for searching images.
@@ -198,8 +200,8 @@ type ImageSearchOptions struct {
Limit int
// NoTrunc will not truncate the output.
NoTrunc bool
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImageSearchReport is the response from searching images.
@@ -218,16 +220,15 @@ type ImageSearchReport struct {
Automated string
}
+// Image List Options
type ImageListOptions struct {
- All bool `json:"all" schema:"all"`
- Filter []string `json:"Filter,omitempty"`
- Filters url.Values `json:"filters" schema:"filters"`
+ All bool `json:"all" schema:"all"`
+ Filter []string `json:"Filter,omitempty"`
}
type ImagePruneOptions struct {
- All bool `json:"all" schema:"all"`
- Filter []string `json:"filter" schema:"filter"`
- Filters url.Values `json:"filters" schema:"filters"`
+ All bool `json:"all" schema:"all"`
+ Filter []string `json:"filter" schema:"filter"`
}
type ImagePruneReport struct {
@@ -284,3 +285,36 @@ type ImageTreeOptions struct {
type ImageTreeReport struct {
Tree string // TODO: Refactor move presentation work out of server
}
+
+// ShowTrustOptions are the cli options for showing trust
+type ShowTrustOptions struct {
+ JSON bool
+ PolicyPath string
+ Raw bool
+ RegistryPath string
+}
+
+// ShowTrustReport describes the results of show trust
+type ShowTrustReport struct {
+ Raw []byte
+ SystemRegistriesDirPath string
+ JSONOutput []byte
+ Policies []*trust.TrustPolicy
+}
+
+// SetTrustOptions describes the CLI options for setting trust
+type SetTrustOptions struct {
+ PolicyPath string
+ PubKeysFile []string
+ Type string
+}
+
+// SignOptions describes input options for the CLI signing
+type SignOptions struct {
+ Directory string
+ SignBy string
+ CertDir string
+}
+
+// SignReport describes the result of signing
+type SignReport struct{}
diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go
index 7316735b0..853619b19 100644
--- a/pkg/domain/entities/manifest.go
+++ b/pkg/domain/entities/manifest.go
@@ -1,5 +1,9 @@
package entities
+import "github.com/containers/image/v5/types"
+
+// TODO: add comments to *all* types and fields.
+
type ManifestCreateOptions struct {
All bool `schema:"all"`
}
@@ -14,3 +18,21 @@ type ManifestAddOptions struct {
OSVersion string `json:"os_version" schema:"os_version"`
Variant string `json:"variant" schema:"variant"`
}
+
+type ManifestAnnotateOptions struct {
+ Annotation []string `json:"annotation"`
+ Arch string `json:"arch" schema:"arch"`
+ Features []string `json:"features" schema:"features"`
+ OS string `json:"os" schema:"os"`
+ OSFeatures []string `json:"os_features" schema:"os_features"`
+ OSVersion string `json:"os_version" schema:"os_version"`
+ Variant string `json:"variant" schema:"variant"`
+}
+
+type ManifestPushOptions struct {
+ Purge, Quiet, All, RemoveSignatures bool
+
+ Authfile, CertDir, Username, Password, DigestFile, Format, SignBy string
+
+ SkipTLSVerify types.OptionalBool
+}
diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go
new file mode 100644
index 000000000..9beeeb042
--- /dev/null
+++ b/pkg/domain/entities/network.go
@@ -0,0 +1,55 @@
+package entities
+
+import (
+ "net"
+
+ "github.com/containernetworking/cni/libcni"
+)
+
+// NetworkListOptions describes options for listing networks in cli
+type NetworkListOptions struct {
+ Format string
+ Quiet bool
+ Filter string
+}
+
+// NetworkListReport describes the results from listing networks
+type NetworkListReport struct {
+ *libcni.NetworkConfigList
+}
+
+// NetworkInspectOptions describes options for inspect networks
+type NetworkInspectOptions struct {
+ Format string
+}
+
+// NetworkInspectReport describes the results from inspect networks
+type NetworkInspectReport map[string]interface{}
+
+// NetworkRmOptions describes options for removing networks
+type NetworkRmOptions struct {
+ Force bool
+}
+
+//NetworkRmReport describes the results of network removal
+type NetworkRmReport struct {
+ Name string
+ Err error
+}
+
+// NetworkCreateOptions describes options to create a network
+// swagger:model NetworkCreateOptions
+type NetworkCreateOptions struct {
+ DisableDNS bool
+ Driver string
+ Gateway net.IP
+ Internal bool
+ MacVLAN string
+ Range net.IPNet
+ Subnet net.IPNet
+}
+
+// NetworkCreateReport describes a created network for the cli
+type NetworkCreateReport struct {
+ Filename string
+}
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
new file mode 100644
index 000000000..4f485cbee
--- /dev/null
+++ b/pkg/domain/entities/play.go
@@ -0,0 +1,37 @@
+package entities
+
+import "github.com/containers/image/v5/types"
+
+// PlayKubeOptions controls playing kube YAML files.
+type PlayKubeOptions struct {
+ // Authfile - path to an authentication file.
+ Authfile string
+ // CertDir - to a directory containing TLS certifications and keys.
+ CertDir string
+ // Username for authenticating against the registry.
+ Username string
+ // Password for authenticating against the registry.
+ Password string
+ // Network - name of the CNI network to connect to.
+ Network string
+ // Quiet - suppress output when pulling images.
+ Quiet bool
+ // SignaturePolicy - path to a signature-policy file.
+ SignaturePolicy string
+ // SkipTLSVerify - skip https and certificate validation when
+ // contacting container registries.
+ SkipTLSVerify types.OptionalBool
+ // SeccompProfileRoot - path to a directory containing seccomp
+ // profiles.
+ SeccompProfileRoot string
+}
+
+// PlayKubeReport contains the results of running play kube.
+type PlayKubeReport struct {
+ // Pod - the ID of the created pod.
+ Pod string
+ // Containers - the IDs of the containers running in the created pod.
+ Containers []string
+ // Logs - non-fatal erros and log messages while processing.
+ Logs []string
+}
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index a4896ce4d..1a38a7aa4 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -184,6 +184,8 @@ type PodInspectOptions struct {
// Options for the API.
NameOrID string
+
+ Format string
}
type PodInspectReport struct {
diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go
index de93a382f..79a90be48 100644
--- a/pkg/domain/entities/system.go
+++ b/pkg/domain/entities/system.go
@@ -3,6 +3,8 @@ package entities
import (
"time"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
@@ -26,3 +28,78 @@ type SystemPruneReport struct {
*ImagePruneReport
VolumePruneReport []*VolumePruneReport
}
+
+// SystemMigrateOptions describes the options needed for the
+// cli to migrate runtimes of containers
+type SystemMigrateOptions struct {
+ NewRuntime string
+}
+
+// SystemDfOptions describes the options for getting df information
+type SystemDfOptions struct {
+ Format string
+ Verbose bool
+}
+
+// SystemDfReport describes the response for df information
+type SystemDfReport struct {
+ Images []*SystemDfImageReport
+ Containers []*SystemDfContainerReport
+ Volumes []*SystemDfVolumeReport
+}
+
+// SystemDfImageReport describes an image for use with df
+type SystemDfImageReport struct {
+ Repository string
+ Tag string
+ ImageID string
+ Created time.Time
+ Size int64
+ SharedSize int64
+ UniqueSize int64
+ Containers int
+}
+
+// SystemDfContainerReport describes a container for use with df
+type SystemDfContainerReport struct {
+ ContainerID string
+ Image string
+ Command []string
+ LocalVolumes int
+ Size int64
+ RWSize int64
+ Created time.Time
+ Status string
+ Names string
+}
+
+// SystemDfVolumeReport describes a volume and its size
+type SystemDfVolumeReport struct {
+ VolumeName string
+ Links int
+ Size int64
+}
+
+// SystemResetOptions describes the options for resetting your
+// container runtime storage, etc
+type SystemResetOptions struct {
+ Force bool
+}
+
+// SystemVersionReport describes version information about the running Podman service
+type SystemVersionReport struct {
+ // Always populated
+ Client *define.Version `json:",omitempty"`
+ // May be populated, when in tunnel mode
+ Server *define.Version `json:",omitempty"`
+}
+
+type ComponentVersion struct {
+ types.Version
+}
+
+// ListRegistriesReport is the report when querying for a sorted list of
+// registries which may be contacted during certain operations.
+type ListRegistriesReport struct {
+ Registries []string
+}
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index 9fbe04c9a..21ab025de 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage/pkg/archive"
- "github.com/cri-o/ocicni/pkg/ocicni"
)
type Container struct {
@@ -40,7 +39,7 @@ type NetOptions struct {
DNSServers []net.IP
Network specgen.Namespace
NoHosts bool
- PublishPorts []ocicni.PortMapping
+ PublishPorts []specgen.PortMapping
StaticIP *net.IP
StaticMAC *net.HardwareAddr
}
diff --git a/pkg/domain/infra/abi/auto-update.go b/pkg/domain/infra/abi/auto-update.go
new file mode 100644
index 000000000..9fcc451fd
--- /dev/null
+++ b/pkg/domain/infra/abi/auto-update.go
@@ -0,0 +1,17 @@
+package abi
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/autoupdate"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) AutoUpdate(ctx context.Context, options entities.AutoUpdateOptions) (*entities.AutoUpdateReport, []error) {
+ // Convert the entities options to the autoupdate ones. We can't use
+ // them in the entities package as low-level packages must not leak
+ // into the remote client.
+ autoOpts := autoupdate.Options{Authfile: options.Authfile}
+ units, failures := autoupdate.AutoUpdate(ic.Libpod, autoOpts)
+ return &entities.AutoUpdateReport{Units: units}, failures
+}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 4c3389418..e982c7c11 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -8,8 +8,7 @@ import (
"strconv"
"strings"
"sync"
-
- lpfilters "github.com/containers/libpod/libpod/filters"
+ "time"
"github.com/containers/buildah"
"github.com/containers/common/pkg/config"
@@ -17,8 +16,10 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
+ lpfilters "github.com/containers/libpod/libpod/filters"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/libpod/logs"
+ "github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/checkpoint"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
@@ -32,9 +33,9 @@ import (
"github.com/sirupsen/logrus"
)
-// getContainersByContext gets pods whether all, latest, or a slice of names/ids
-// is specified.
-func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) {
+// getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids
+// is specified. It also returns a list of the corresponding input name used to lookup each container.
+func getContainersAndInputByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) {
var ctr *libpod.Container
ctrs = []*libpod.Container{}
@@ -43,6 +44,7 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru
ctrs, err = runtime.GetAllContainers()
case latest:
ctr, err = runtime.GetLatestContainer()
+ rawInput = append(rawInput, ctr.ID())
ctrs = append(ctrs, ctr)
default:
for _, n := range names {
@@ -54,6 +56,7 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru
err = e
}
} else {
+ rawInput = append(rawInput, n)
ctrs = append(ctrs, ctr)
}
}
@@ -61,6 +64,13 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru
return
}
+// getContainersByContext gets containers whether all, latest, or a slice of names/ids
+// is specified.
+func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) {
+ ctrs, _, err = getContainersAndInputByContext(all, latest, names, runtime)
+ return
+}
+
// TODO: Should return *entities.ContainerExistsReport, error
func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
_, err := ic.Libpod.LookupContainer(nameOrId)
@@ -184,6 +194,10 @@ func (ic *ContainerEngine) ContainerPrune(ctx context.Context, options entities.
filterFuncs = append(filterFuncs, generatedFunc)
}
}
+ return ic.pruneContainersHelper(ctx, filterFuncs)
+}
+
+func (ic *ContainerEngine) pruneContainersHelper(ctx context.Context, filterFuncs []libpod.ContainerFilter) (*entities.ContainerPruneReport, error) {
prunedContainers, pruneErrors, err := ic.Libpod.PruneContainers(filterFuncs)
if err != nil {
return nil, err
@@ -420,6 +434,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
TCPEstablished: options.TCPEstablished,
TargetFile: options.Export,
IgnoreRootfs: options.IgnoreRootFS,
+ KeepRunning: options.LeaveRunning,
}
if options.All {
@@ -514,13 +529,29 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string,
}
// If the container is in a pod, also set to recursively start dependencies
- if err := terminal.StartAttachCtr(ctx, ctr, options.Stdin, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
+ err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != "")
+ if err != nil && errors.Cause(err) != define.ErrDetach {
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
}
return nil
}
-func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) {
+func makeExecConfig(options entities.ExecOptions) *libpod.ExecConfig {
+ execConfig := new(libpod.ExecConfig)
+ execConfig.Command = options.Cmd
+ execConfig.Terminal = options.Tty
+ execConfig.Privileged = options.Privileged
+ execConfig.Environment = options.Envs
+ execConfig.User = options.User
+ execConfig.WorkDir = options.WorkDir
+ execConfig.DetachKeys = &options.DetachKeys
+ execConfig.PreserveFDs = options.PreserveFDs
+ execConfig.AttachStdin = options.Interactive
+
+ return execConfig
+}
+
+func checkExecPreserveFDs(options entities.ExecOptions) (int, error) {
ec := define.ExecErrorCodeGeneric
if options.PreserveFDs > 0 {
entries, err := ioutil.ReadDir("/proc/self/fd")
@@ -543,24 +574,77 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o
}
}
}
+ return ec, nil
+}
+
+func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) {
+ ec, err := checkExecPreserveFDs(options)
+ if err != nil {
+ return ec, err
+ }
ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod)
if err != nil {
return ec, err
}
ctr := ctrs[0]
- ec, err = terminal.ExecAttachCtr(ctx, ctr, options.Tty, options.Privileged, options.Envs, options.Cmd, options.User, options.WorkDir, &options.Streams, options.PreserveFDs, options.DetachKeys)
+
+ execConfig := makeExecConfig(options)
+
+ ec, err = terminal.ExecAttachCtr(ctx, ctr, execConfig, &streams)
return define.TranslateExecErrorToExitCode(ec, err), err
}
+func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrId string, options entities.ExecOptions) (string, error) {
+ _, err := checkExecPreserveFDs(options)
+ if err != nil {
+ return "", err
+ }
+ ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod)
+ if err != nil {
+ return "", err
+ }
+ ctr := ctrs[0]
+
+ execConfig := makeExecConfig(options)
+
+ // Make an exit command
+ storageConfig := ic.Libpod.StorageConfig()
+ runtimeConfig, err := ic.Libpod.GetConfig()
+ if err != nil {
+ return "", errors.Wrapf(err, "error retrieving Libpod configuration to build exec exit command")
+ }
+ podmanPath, err := os.Executable()
+ if err != nil {
+ return "", errors.Wrapf(err, "error retrieving executable to build exec exit command")
+ }
+ // TODO: Add some ability to toggle syslog
+ exitCommandArgs := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, podmanPath, false, true, true)
+ execConfig.ExitCommand = exitCommandArgs
+
+ // Create and start the exec session
+ id, err := ctr.ExecCreate(execConfig)
+ if err != nil {
+ return "", err
+ }
+
+ // TODO: we should try and retrieve exit code if this fails.
+ if err := ctr.ExecStart(id); err != nil {
+ return "", err
+ }
+ return id, nil
+}
+
func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) {
var reports []*entities.ContainerStartReport
var exitCode = define.ExecErrorCodeGeneric
- ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
+ ctrs, rawInputs, err := getContainersAndInputByContext(false, options.Latest, namesOrIds, ic.Libpod)
if err != nil {
return nil, err
}
// There can only be one container if attach was used
- for _, ctr := range ctrs {
+ for i := range ctrs {
+ ctr := ctrs[i]
+ rawInput := rawInputs[i]
ctrState, err := ctr.State()
if err != nil {
return nil, err
@@ -574,6 +658,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
// Exit cleanly immediately
reports = append(reports, &entities.ContainerStartReport{
Id: ctr.ID(),
+ RawInput: rawInput,
Err: nil,
ExitCode: 0,
})
@@ -584,6 +669,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
logrus.Debugf("Deadlock error: %v", err)
reports = append(reports, &entities.ContainerStartReport{
Id: ctr.ID(),
+ RawInput: rawInput,
Err: err,
ExitCode: define.ExitCode(err),
})
@@ -593,6 +679,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
if ctrRunning {
reports = append(reports, &entities.ContainerStartReport{
Id: ctr.ID(),
+ RawInput: rawInput,
Err: nil,
ExitCode: 0,
})
@@ -602,6 +689,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
if err != nil {
reports = append(reports, &entities.ContainerStartReport{
Id: ctr.ID(),
+ RawInput: rawInput,
Err: err,
ExitCode: exitCode,
})
@@ -624,6 +712,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
}
reports = append(reports, &entities.ContainerStartReport{
Id: ctr.ID(),
+ RawInput: rawInput,
Err: err,
ExitCode: exitCode,
})
@@ -636,6 +725,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
// If the container is in a pod, also set to recursively start dependencies
report := &entities.ContainerStartReport{
Id: ctr.ID(),
+ RawInput: rawInput,
ExitCode: 125,
}
if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil {
@@ -812,6 +902,20 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st
for _, ctr := range ctrs {
var err error
report := entities.ContainerCleanupReport{Id: ctr.ID()}
+
+ if options.Exec != "" {
+ if options.Remove {
+ if err := ctr.ExecRemove(options.Exec, false); err != nil {
+ return nil, err
+ }
+ } else {
+ if err := ctr.ExecCleanup(options.Exec); err != nil {
+ return nil, err
+ }
+ }
+ return []*entities.ContainerCleanupReport{}, nil
+ }
+
if options.Remove {
err = ic.Libpod.RemoveContainer(ctx, ctr, false, true)
if err != nil {
@@ -981,3 +1085,77 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) {
_ = ic.Libpod.Shutdown(false)
})
}
+
+func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error {
+ defer close(options.StatChan)
+ containerFunc := ic.Libpod.GetRunningContainers
+ switch {
+ case len(namesOrIds) > 0:
+ containerFunc = func() ([]*libpod.Container, error) { return ic.Libpod.GetContainersByList(namesOrIds) }
+ case options.Latest:
+ containerFunc = func() ([]*libpod.Container, error) {
+ lastCtr, err := ic.Libpod.GetLatestContainer()
+ if err != nil {
+ return nil, err
+ }
+ return []*libpod.Container{lastCtr}, nil
+ }
+ case options.All:
+ containerFunc = ic.Libpod.GetAllContainers
+ }
+
+ ctrs, err := containerFunc()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get list of containers")
+ }
+ containerStats := map[string]*define.ContainerStats{}
+ for _, ctr := range ctrs {
+ initialStats, err := ctr.GetContainerStats(&define.ContainerStats{})
+ if err != nil {
+ // when doing "all", don't worry about containers that are not running
+ cause := errors.Cause(err)
+ if options.All && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) {
+ continue
+ }
+ if cause == cgroups.ErrCgroupV1Rootless {
+ err = cause
+ }
+ return err
+ }
+ containerStats[ctr.ID()] = initialStats
+ }
+ for {
+ reportStats := []*define.ContainerStats{}
+ for _, ctr := range ctrs {
+ id := ctr.ID()
+ if _, ok := containerStats[ctr.ID()]; !ok {
+ initialStats, err := ctr.GetContainerStats(&define.ContainerStats{})
+ if errors.Cause(err) == define.ErrCtrRemoved || errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrStateInvalid {
+ // skip dealing with a container that is gone
+ continue
+ }
+ if err != nil {
+ return err
+ }
+ containerStats[id] = initialStats
+ }
+ stats, err := ctr.GetContainerStats(containerStats[id])
+ if err != nil && errors.Cause(err) != define.ErrNoSuchCtr {
+ return err
+ }
+ // replace the previous measurement with the current one
+ containerStats[id] = stats
+ reportStats = append(reportStats, stats)
+ }
+ ctrs, err = containerFunc()
+ if err != nil {
+ return err
+ }
+ options.StatChan <- reportStats
+ if options.NoStream {
+ break
+ }
+ time.Sleep(time.Second)
+ }
+ return nil
+}
diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go
new file mode 100644
index 000000000..41f4444d5
--- /dev/null
+++ b/pkg/domain/infra/abi/containers_runlabel.go
@@ -0,0 +1,280 @@
+package abi
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/domain/entities"
+ envLib "github.com/containers/libpod/pkg/env"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/containers/libpod/utils"
+ "github.com/google/shlex"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, imageRef string, args []string, options entities.ContainerRunlabelOptions) error {
+ // First, get the image and pull it if needed.
+ img, err := ic.runlabelImage(ctx, label, imageRef, options)
+ if err != nil {
+ return err
+ }
+ // Extract the runlabel from the image.
+ runlabel, err := img.GetLabel(ctx, label)
+ if err != nil {
+ return err
+ }
+
+ cmd, env, err := generateRunlabelCommand(runlabel, img, args, options)
+ if err != nil {
+ return err
+ }
+
+ stdErr := os.Stderr
+ stdOut := os.Stdout
+ stdIn := os.Stdin
+ if options.Quiet {
+ stdErr = nil
+ stdOut = nil
+ stdIn = nil
+ }
+
+ // If container already exists && --replace given -- Nuke it
+ if options.Replace {
+ for i, entry := range cmd {
+ if entry == "--name" {
+ name := cmd[i+1]
+ ctr, err := ic.Libpod.LookupContainer(name)
+ if err != nil {
+ if errors.Cause(err) != define.ErrNoSuchCtr {
+ logrus.Debugf("Error occurred searching for container %s: %s", name, err.Error())
+ return err
+ }
+ } else {
+ logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name)
+ if err := ic.Libpod.RemoveContainer(ctx, ctr, true, false); err != nil {
+ return err
+ }
+ }
+ break
+ }
+ }
+ }
+
+ return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...)
+}
+
+// runlabelImage returns an image based on the specified image AND options.
+func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imageRef string, options entities.ContainerRunlabelOptions) (*image.Image, error) {
+ // First, look up the image locally. If we get an error and requested
+ // to pull, fallthrough and pull it.
+ img, err := ic.Libpod.ImageRuntime().NewFromLocal(imageRef)
+ switch {
+ case err == nil:
+ return img, nil
+ case !options.Pull:
+ return nil, err
+ default:
+ // Fallthrough and pull!
+ }
+
+ // Parse credentials if specified.
+ var credentials *types.DockerAuthConfig
+ if options.Credentials != "" {
+ credentials, err = util.ParseRegistryCreds(options.Credentials)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Suppress pull progress bars if requested.
+ pullOutput := os.Stdout
+ if options.Quiet {
+ pullOutput = nil // c/image/copy takes care of the rest
+ }
+
+ // Pull the image.
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerCertPath: options.CertDir,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
+ DockerRegistryCreds: credentials,
+ }
+
+ return ic.Libpod.ImageRuntime().New(ctx, imageRef, options.SignaturePolicy, options.Authfile, pullOutput, &dockerRegistryOptions, image.SigningOptions{}, &label, util.PullImageMissing)
+}
+
+// generateRunlabelCommand generates the to-be-executed command as a string
+// slice along with a base environment.
+func generateRunlabelCommand(runlabel string, img *image.Image, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) {
+ var (
+ err error
+ name, imageName string
+ globalOpts string
+ cmd, env []string
+ )
+
+ // TODO: How do we get global opts as done in v1?
+
+ // Extract the imageName (or ID).
+ imgNames := img.Names()
+ if len(imgNames) == 0 {
+ imageName = img.ID()
+ } else {
+ imageName = imgNames[0]
+ }
+
+ // Use the user-specified name or extract one from the image.
+ if options.Name != "" {
+ name = options.Name
+ } else {
+ name, err = image.GetImageBaseName(imageName)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Append the user-specified arguments to the runlabel (command).
+ if len(args) > 0 {
+ runlabel = fmt.Sprintf("%s %s", runlabel, strings.Join(args, " "))
+ }
+
+ cmd, err = generateCommand(runlabel, imageName, name, globalOpts)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ env = generateRunEnvironment(name, imageName, options)
+ env = append(env, "PODMAN_RUNLABEL_NESTED=1")
+ envmap, err := envLib.ParseSlice(env)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ envmapper := func(k string) string {
+ switch k {
+ case "OPT1":
+ return envmap["OPT1"]
+ case "OPT2":
+ return envmap["OPT2"]
+ case "OPT3":
+ return envmap["OPT3"]
+ case "PWD":
+ // I would prefer to use os.getenv but it appears PWD is not in the os env list.
+ d, err := os.Getwd()
+ if err != nil {
+ logrus.Error("unable to determine current working directory")
+ return ""
+ }
+ return d
+ }
+ return ""
+ }
+ newS := os.Expand(strings.Join(cmd, " "), envmapper)
+ cmd, err = shlex.Split(newS)
+ if err != nil {
+ return nil, nil, err
+ }
+ return cmd, env, nil
+}
+
+// generateCommand takes a label (string) and converts it to an executable command
+func generateCommand(command, imageName, name, globalOpts string) ([]string, error) {
+ var (
+ newCommand []string
+ )
+ if name == "" {
+ name = imageName
+ }
+
+ cmd, err := shlex.Split(command)
+ if err != nil {
+ return nil, err
+ }
+
+ prog, err := substituteCommand(cmd[0])
+ if err != nil {
+ return nil, err
+ }
+ newCommand = append(newCommand, prog)
+
+ for _, arg := range cmd[1:] {
+ var newArg string
+ switch arg {
+ case "IMAGE":
+ newArg = imageName
+ case "$IMAGE":
+ newArg = imageName
+ case "IMAGE=IMAGE":
+ newArg = fmt.Sprintf("IMAGE=%s", imageName)
+ case "IMAGE=$IMAGE":
+ newArg = fmt.Sprintf("IMAGE=%s", imageName)
+ case "NAME":
+ newArg = name
+ case "NAME=NAME":
+ newArg = fmt.Sprintf("NAME=%s", name)
+ case "NAME=$NAME":
+ newArg = fmt.Sprintf("NAME=%s", name)
+ case "$NAME":
+ newArg = name
+ case "$GLOBAL_OPTS":
+ newArg = globalOpts
+ default:
+ newArg = arg
+ }
+ newCommand = append(newCommand, newArg)
+ }
+ return newCommand, nil
+}
+
+// GenerateRunEnvironment merges the current environment variables with optional
+// environment variables provided by the user
+func generateRunEnvironment(name, imageName string, options entities.ContainerRunlabelOptions) []string {
+ newEnv := os.Environ()
+ if options.Optional1 != "" {
+ newEnv = append(newEnv, fmt.Sprintf("OPT1=%s", options.Optional1))
+ }
+ if options.Optional2 != "" {
+ newEnv = append(newEnv, fmt.Sprintf("OPT2=%s", options.Optional2))
+ }
+ if options.Optional3 != "" {
+ newEnv = append(newEnv, fmt.Sprintf("OPT3=%s", options.Optional3))
+ }
+ return newEnv
+}
+
+func substituteCommand(cmd string) (string, error) {
+ var (
+ newCommand string
+ )
+
+ // Replace cmd with "/proc/self/exe" if "podman" or "docker" is being
+ // used. If "/usr/bin/docker" is provided, we also sub in podman.
+ // Otherwise, leave the command unchanged.
+ if cmd == "podman" || filepath.Base(cmd) == "docker" {
+ newCommand = "/proc/self/exe"
+ } else {
+ newCommand = cmd
+ }
+
+ // If cmd is an absolute or relative path, check if the file exists.
+ // Throw an error if it doesn't exist.
+ if strings.Contains(newCommand, "/") || strings.HasPrefix(newCommand, ".") {
+ res, err := filepath.Abs(newCommand)
+ if err != nil {
+ return "", err
+ }
+ if _, err := os.Stat(res); !os.IsNotExist(err) {
+ return res, nil
+ } else if err != nil {
+ return "", err
+ }
+ }
+
+ return newCommand, nil
+}
diff --git a/pkg/domain/infra/abi/events.go b/pkg/domain/infra/abi/events.go
index 20773cdce..7ec9db369 100644
--- a/pkg/domain/infra/abi/events.go
+++ b/pkg/domain/infra/abi/events.go
@@ -5,12 +5,9 @@ import (
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/domain/entities"
- "github.com/sirupsen/logrus"
)
func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptions) error {
readOpts := events.ReadOptions{FromStart: opts.FromStart, Stream: opts.Stream, Filters: opts.Filter, EventChannel: opts.EventChan, Since: opts.Since, Until: opts.Until}
- err := ic.Libpod.Events(readOpts)
- logrus.Error(err)
- return err
+ return ic.Libpod.Events(readOpts)
}
diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go
index f69ba560e..abb5e2911 100644
--- a/pkg/domain/infra/abi/generate.go
+++ b/pkg/domain/infra/abi/generate.go
@@ -1,14 +1,18 @@
package abi
import (
+ "bytes"
"context"
"fmt"
"strings"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/systemd/generate"
+ "github.com/ghodss/yaml"
"github.com/pkg/errors"
+ k8sAPI "k8s.io/api/core/v1"
)
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
@@ -155,14 +159,14 @@ func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod
func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) {
var kind, name, ctrName string
if pod == nil {
- kind = "container"
+ kind = options.ContainerPrefix //defaults to container
name = ctr.ID()
if options.Name {
name = ctr.Name()
}
ctrName = name
} else {
- kind = "pod"
+ kind = options.PodPrefix //defaults to pod
name = pod.ID()
ctrName = ctr.ID()
if options.Name {
@@ -170,5 +174,86 @@ func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entitie
ctrName = ctr.Name()
}
}
- return ctrName, fmt.Sprintf("%s-%s", kind, name)
+ return ctrName, fmt.Sprintf("%s%s%s", kind, options.Separator, name)
+}
+
+func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+ var (
+ pod *libpod.Pod
+ podYAML *k8sAPI.Pod
+ err error
+ ctr *libpod.Container
+ servicePorts []k8sAPI.ServicePort
+ serviceYAML k8sAPI.Service
+ )
+ // Get the container in question.
+ ctr, err = ic.Libpod.LookupContainer(nameOrID)
+ if err != nil {
+ pod, err = ic.Libpod.LookupPod(nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ podYAML, servicePorts, err = pod.GenerateForKube()
+ } else {
+ if len(ctr.Dependencies()) > 0 {
+ return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
+ }
+ podYAML, err = ctr.GenerateForKube()
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if options.Service {
+ serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts)
+ }
+
+ content, err := generateKubeOutput(podYAML, &serviceYAML)
+ if err != nil {
+ return nil, err
+ }
+
+ return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil
+}
+
+func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service) ([]byte, error) {
+ var (
+ output []byte
+ marshalledPod []byte
+ marshalledService []byte
+ err error
+ )
+
+ marshalledPod, err = yaml.Marshal(podYAML)
+ if err != nil {
+ return nil, err
+ }
+
+ if serviceYAML != nil {
+ marshalledService, err = yaml.Marshal(serviceYAML)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ header := `# Generation of Kubernetes YAML is still under development!
+#
+# Save the output of this file and use kubectl create -f to import
+# it into Kubernetes.
+#
+# Created with podman-%s
+`
+ podmanVersion, err := define.GetVersion()
+ if err != nil {
+ return nil, err
+ }
+
+ output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...)
+ output = append(output, marshalledPod...)
+ if serviceYAML != nil {
+ output = append(output, []byte("---\n")...)
+ output = append(output, marshalledService...)
+ }
+
+ return output, nil
}
diff --git a/pkg/domain/infra/abi/healthcheck.go b/pkg/domain/infra/abi/healthcheck.go
index 351bf4f7e..4e925ef56 100644
--- a/pkg/domain/infra/abi/healthcheck.go
+++ b/pkg/domain/infra/abi/healthcheck.go
@@ -3,7 +3,6 @@ package abi
import (
"context"
- "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
)
@@ -13,9 +12,9 @@ func (ic *ContainerEngine) HealthCheckRun(ctx context.Context, nameOrId string,
if err != nil {
return nil, err
}
- hcStatus := "unhealthy"
- if status == libpod.HealthCheckSuccess {
- hcStatus = "healthy"
+ hcStatus := define.HealthCheckUnhealthy
+ if status == define.HealthCheckSuccess {
+ hcStatus = define.HealthCheckHealthy
}
report := define.HealthCheckResults{
Status: hcStatus,
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 7c63276e5..d8af4d339 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -4,14 +4,22 @@ import (
"context"
"fmt"
"io"
+ "io/ioutil"
+ "net/url"
"os"
+ "path/filepath"
+ "strconv"
"strings"
+ "github.com/containers/libpod/pkg/rootless"
+
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker"
dockerarchive "github.com/containers/image/v5/docker/archive"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
+ "github.com/containers/image/v5/signature"
+ "github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod/define"
@@ -19,14 +27,17 @@ import (
libpodImage "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/domain/entities"
domainUtils "github.com/containers/libpod/pkg/domain/utils"
+ "github.com/containers/libpod/pkg/trust"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
- "github.com/hashicorp/go-multierror"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+// SignatureStoreDir defines default directory to store signatures
+const SignatureStoreDir = "/var/lib/containers/sigstore"
+
func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) {
_, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
if err != nil && errors.Cause(err) != define.ErrNoSuchImage {
@@ -36,7 +47,11 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
}
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
- results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter)
+ return ir.pruneImagesHelper(ctx, opts.All, opts.Filter)
+}
+
+func (ir *ImageEngine) pruneImagesHelper(ctx context.Context, all bool, filters []string) (*entities.ImagePruneReport, error) {
+ results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, all, filters)
if err != nil {
return nil, err
}
@@ -106,19 +121,18 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
}
var registryCreds *types.DockerAuthConfig
- if options.Credentials != "" {
- creds, err := util.ParseRegistryCreds(options.Credentials)
- if err != nil {
- return nil, err
+ if len(options.Username) > 0 && len(options.Password) > 0 {
+ registryCreds = &types.DockerAuthConfig{
+ Username: options.Username,
+ Password: options.Password,
}
- registryCreds = creds
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
OSChoice: options.OverrideOS,
ArchitectureChoice: options.OverrideArch,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
if !options.AllTags {
@@ -211,17 +225,16 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
}
var registryCreds *types.DockerAuthConfig
- if options.Credentials != "" {
- creds, err := util.ParseRegistryCreds(options.Credentials)
- if err != nil {
- return err
+ if len(options.Username) > 0 && len(options.Password) > 0 {
+ registryCreds = &types.DockerAuthConfig{
+ Username: options.Username,
+ Password: options.Password,
}
- registryCreds = creds
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
signOptions := image.SigningOptions{
@@ -370,7 +383,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
Filter: *filter,
Limit: opts.Limit,
NoTrunc: opts.NoTrunc,
- InsecureSkipTLSVerify: opts.TLSVerify,
+ InsecureSkipTLSVerify: opts.SkipTLSVerify,
}
searchResults, err := image.SearchImages(term, searchOpts)
@@ -419,8 +432,10 @@ func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.
return &entities.ImageTreeReport{Tree: results}, nil
}
-// Remove removes one or more images from local storage.
-func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) {
+// removeErrorsToExitCode returns an exit code for the specified slice of
+// image-removal errors. The error codes are set according to the documented
+// behaviour in the Podman man pages.
+func removeErrorsToExitCode(rmErrors []error) int {
var (
// noSuchImageErrors indicates that at least one image was not found.
noSuchImageErrors bool
@@ -430,57 +445,53 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
// otherErrors indicates that at least one error other than the two
// above occured.
otherErrors bool
- // deleteError is a multierror to conveniently collect errors during
- // removal. We really want to delete as many images as possible and not
- // error out immediately.
- deleteError *multierror.Error
)
- report = &entities.ImageRemoveReport{}
+ if len(rmErrors) == 0 {
+ return 0
+ }
- // Set the removalCode and the error after all work is done.
- defer func() {
- switch {
- // 2
- case inUseErrors:
- // One of the specified images has child images or is
- // being used by a container.
- report.ExitCode = 2
- // 1
- case noSuchImageErrors && !(otherErrors || inUseErrors):
- // One of the specified images did not exist, and no other
- // failures.
- report.ExitCode = 1
- // 0
+ for _, e := range rmErrors {
+ switch errors.Cause(e) {
+ case define.ErrNoSuchImage:
+ noSuchImageErrors = true
+ case define.ErrImageInUse, storage.ErrImageUsedByContainer:
+ inUseErrors = true
default:
- // Nothing to do.
- }
- if deleteError != nil {
- // go-multierror has a trailing new line which we need to remove to normalize the string.
- finalError = deleteError.ErrorOrNil()
- finalError = errors.New(strings.TrimSpace(finalError.Error()))
+ otherErrors = true
}
+ }
+
+ switch {
+ case inUseErrors:
+ // One of the specified images has child images or is
+ // being used by a container.
+ return 2
+ case noSuchImageErrors && !(otherErrors || inUseErrors):
+ // One of the specified images did not exist, and no other
+ // failures.
+ return 1
+ default:
+ return 125
+ }
+}
+
+// Remove removes one or more images from local storage.
+func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) {
+ report = &entities.ImageRemoveReport{}
+
+ // Set the exit code at very end.
+ defer func() {
+ report.ExitCode = removeErrorsToExitCode(rmErrors)
}()
// deleteImage is an anonymous function to conveniently delete an image
// without having to pass all local data around.
deleteImage := func(img *image.Image) error {
results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
- switch errors.Cause(err) {
- case nil:
- break
- case storage.ErrImageUsedByContainer:
- inUseErrors = true // Important for exit codes in Podman.
- return errors.New(
- fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
- case define.ErrImageInUse:
- inUseErrors = true
- return err
- default:
- otherErrors = true // Important for exit codes in Podman.
+ if err != nil {
return err
}
-
report.Deleted = append(report.Deleted, results.Deleted)
report.Untagged = append(report.Untagged, results.Untagged...)
return nil
@@ -493,9 +504,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
for {
storageImages, err := ir.Libpod.ImageRuntime().GetRWImages()
if err != nil {
- deleteError = multierror.Append(deleteError,
- errors.Wrapf(err, "unable to query local images"))
- otherErrors = true // Important for exit codes in Podman.
+ rmErrors = append(rmErrors, err)
return
}
// No images (left) to remove, so we're done.
@@ -504,9 +513,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
}
// Prevent infinity loops by making a delete-progress check.
if previousImages == len(storageImages) {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError,
- errors.New("unable to delete all images, check errors and re-run image removal if needed"))
+ rmErrors = append(rmErrors, errors.New("unable to delete all images, check errors and re-run image removal if needed"))
break
}
previousImages = len(storageImages)
@@ -514,15 +521,15 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
for _, img := range storageImages {
isParent, err := img.IsParent(ctx)
if err != nil {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
+ continue
}
// Skip parent images.
if isParent {
continue
}
if err := deleteImage(img); err != nil {
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
}
}
}
@@ -533,21 +540,13 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
// Delete only the specified images.
for _, id := range images {
img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
- switch errors.Cause(err) {
- case nil:
- break
- case image.ErrNoSuchImage:
- noSuchImageErrors = true // Important for exit codes in Podman.
- fallthrough
- default:
- deleteError = multierror.Append(deleteError, err)
+ if err != nil {
+ rmErrors = append(rmErrors, err)
continue
}
-
err = deleteImage(img)
if err != nil {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
}
}
@@ -560,3 +559,145 @@ func (ir *ImageEngine) Shutdown(_ context.Context) {
_ = ir.Libpod.Shutdown(false)
})
}
+
+func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) {
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerCertPath: options.CertDir,
+ }
+
+ mech, err := signature.NewGPGSigningMechanism()
+ if err != nil {
+ return nil, errors.Wrap(err, "error initializing GPG")
+ }
+ defer mech.Close()
+ if err := mech.SupportsSigning(); err != nil {
+ return nil, errors.Wrap(err, "signing is not supported")
+ }
+ sc := ir.Libpod.SystemContext()
+ sc.DockerCertPath = options.CertDir
+
+ systemRegistriesDirPath := trust.RegistriesDirPath(sc)
+ registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error reading registry configuration")
+ }
+
+ for _, signimage := range names {
+ var sigStoreDir string
+ srcRef, err := alltransports.ParseImageName(signimage)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing image name")
+ }
+ rawSource, err := srcRef.NewImageSource(ctx, sc)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting image source")
+ }
+ err = rawSource.Close()
+ if err != nil {
+ logrus.Errorf("unable to close new image source %q", err)
+ }
+ getManifest, _, err := rawSource.GetManifest(ctx, nil)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting getManifest")
+ }
+ dockerReference := rawSource.Reference().DockerReference()
+ if dockerReference == nil {
+ return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference()))
+ }
+
+ // create the signstore file
+ rtc, err := ir.Libpod.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ newImage, err := ir.Libpod.ImageRuntime().New(ctx, signimage, rtc.Engine.SignaturePolicyPath, "", os.Stderr, &dockerRegistryOptions, image.SigningOptions{SignBy: options.SignBy}, nil, util.PullImageMissing)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error pulling image %s", signimage)
+ }
+ if sigStoreDir == "" {
+ if rootless.IsRootless() {
+ sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore")
+ } else {
+ registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs)
+ if registryInfo != nil {
+ if sigStoreDir = registryInfo.SigStoreStaging; sigStoreDir == "" {
+ sigStoreDir = registryInfo.SigStore
+
+ }
+ }
+ }
+ }
+ sigStoreDir, err = isValidSigStoreDir(sigStoreDir)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreDir)
+ }
+ repos, err := newImage.RepoDigests()
+ if err != nil {
+ return nil, errors.Wrapf(err, "error calculating repo digests for %s", signimage)
+ }
+ if len(repos) == 0 {
+ logrus.Errorf("no repodigests associated with the image %s", signimage)
+ continue
+ }
+
+ // create signature
+ newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error creating new signature")
+ }
+
+ trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0])
+ sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1))
+ if err := os.MkdirAll(sigStoreDir, 0751); err != nil {
+ // The directory is allowed to exist
+ if !os.IsExist(err) {
+ logrus.Errorf("error creating directory %s: %s", sigStoreDir, err)
+ continue
+ }
+ }
+ sigFilename, err := getSigFilename(sigStoreDir)
+ if err != nil {
+ logrus.Errorf("error creating sigstore file: %v", err)
+ continue
+ }
+ err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644)
+ if err != nil {
+ logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
+ continue
+ }
+ }
+ return nil, nil
+}
+
+func getSigFilename(sigStoreDirPath string) (string, error) {
+ sigFileSuffix := 1
+ sigFiles, err := ioutil.ReadDir(sigStoreDirPath)
+ if err != nil {
+ return "", err
+ }
+ sigFilenames := make(map[string]bool)
+ for _, file := range sigFiles {
+ sigFilenames[file.Name()] = true
+ }
+ for {
+ sigFilename := "signature-" + strconv.Itoa(sigFileSuffix)
+ if _, exists := sigFilenames[sigFilename]; !exists {
+ return sigFilename, nil
+ }
+ sigFileSuffix++
+ }
+}
+
+func isValidSigStoreDir(sigStoreDir string) (string, error) {
+ writeURIs := map[string]bool{"file": true}
+ url, err := url.Parse(sigStoreDir)
+ if err != nil {
+ return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir)
+ }
+ _, exists := writeURIs[url.Scheme]
+ if !exists {
+ return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir)
+ }
+ sigStoreDir = url.Path
+ return sigStoreDir, nil
+}
diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go
index 9add915ea..3034e36ec 100644
--- a/pkg/domain/infra/abi/images_list.go
+++ b/pkg/domain/infra/abi/images_list.go
@@ -13,20 +13,13 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
err error
)
- // TODO: Future work support for domain.Filters
- // filters := utils.ToLibpodFilters(opts.Filters)
-
- if len(opts.Filter) > 0 {
- images, err = ir.Libpod.ImageRuntime().GetImagesWithFilters(opts.Filter)
- } else {
- images, err = ir.Libpod.ImageRuntime().GetImages()
- }
+ images, err = ir.Libpod.ImageRuntime().GetImagesWithFilters(opts.Filter)
if err != nil {
return nil, err
}
- summaries := make([]*entities.ImageSummary, len(images))
- for i, img := range images {
+ var summaries []*entities.ImageSummary
+ for _, img := range images {
var repoTags []string
if opts.All {
pairs, err := libpodImage.ReposToMap(img.Names())
@@ -40,7 +33,19 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
}
}
} else {
- repoTags, _ = img.RepoTags()
+ repoTags, err = img.RepoTags()
+ if err != nil {
+ return nil, err
+ }
+ if len(img.Names()) == 0 {
+ parent, err := img.IsParent(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if parent {
+ continue
+ }
+ }
}
digests := make([]string, len(img.Digests()))
@@ -72,7 +77,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
sz, _ := img.Size(context.TODO())
e.Size = int64(*sz)
- summaries[i] = &e
+ summaries = append(summaries, &e)
}
return summaries, nil
}
diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go
index 88331f96c..6e311dec7 100644
--- a/pkg/domain/infra/abi/manifest.go
+++ b/pkg/domain/infra/abi/manifest.go
@@ -6,14 +6,22 @@ import (
"context"
"encoding/json"
"fmt"
+ "io/ioutil"
+ "os"
"strings"
+ "github.com/containers/buildah/manifests"
buildahUtil "github.com/containers/buildah/util"
+ cp "github.com/containers/image/v5/copy"
"github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/image/v5/types"
libpodImage "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
+ "github.com/opencontainers/go-digest"
+ imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
@@ -71,7 +79,7 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
}
listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec)
if err != nil {
- return "", errors.Wrapf(err, "error retriving local image from image name %s", listImageSpec)
+ return "", errors.Wrapf(err, "error retrieving local image from image name %s", listImageSpec)
}
manifestAddOpts := libpodImage.ManifestAddOpts{
@@ -100,3 +108,120 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
}
return listID, nil
}
+
+// ManifestAnnotate updates an entry of the manifest list
+func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) {
+ listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
+ if err != nil {
+ return "", errors.Wrapf(err, "error retreiving local image from image name %s", names[0])
+ }
+ digest, err := digest.Parse(names[1])
+ if err != nil {
+ return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
+ }
+ manifestAnnotateOpts := libpodImage.ManifestAnnotateOpts{
+ Arch: opts.Arch,
+ Features: opts.Features,
+ OS: opts.OS,
+ OSFeatures: opts.OSFeatures,
+ OSVersion: opts.OSVersion,
+ Variant: opts.Variant,
+ }
+ if len(opts.Annotation) > 0 {
+ annotations := make(map[string]string)
+ for _, annotationSpec := range opts.Annotation {
+ spec := strings.SplitN(annotationSpec, "=", 2)
+ if len(spec) != 2 {
+ return "", errors.Errorf("no value given for annotation %q", spec[0])
+ }
+ annotations[spec[0]] = spec[1]
+ }
+ manifestAnnotateOpts.Annotation = annotations
+ }
+ updatedListID, err := listImage.AnnotateManifest(*ir.Libpod.SystemContext(), digest, manifestAnnotateOpts)
+ if err == nil {
+ return fmt.Sprintf("%s: %s", updatedListID, digest.String()), nil
+ }
+ return "", err
+}
+
+// ManifestRemove removes specified digest from the specified manifest list
+func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) {
+ instanceDigest, err := digest.Parse(names[1])
+ if err != nil {
+ return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
+ }
+ listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
+ if err != nil {
+ return "", errors.Wrapf(err, "error retriving local image from image name %s", names[0])
+ }
+ updatedListID, err := listImage.RemoveManifest(instanceDigest)
+ if err == nil {
+ return fmt.Sprintf("%s :%s\n", updatedListID, instanceDigest.String()), nil
+ }
+ return "", err
+}
+
+// ManifestPush pushes a manifest list or image index to the destination
+func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts entities.ManifestPushOptions) error {
+ listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
+ if err != nil {
+ return errors.Wrapf(err, "error retriving local image from image name %s", names[0])
+ }
+ dest, err := alltransports.ParseImageName(names[1])
+ if err != nil {
+ return err
+ }
+ var manifestType string
+ if opts.Format != "" {
+ switch opts.Format {
+ case "oci":
+ manifestType = imgspecv1.MediaTypeImageManifest
+ case "v2s2", "docker":
+ manifestType = manifest.DockerV2Schema2MediaType
+ default:
+ return errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format)
+ }
+ }
+
+ // Set the system context.
+ sys := ir.Libpod.SystemContext()
+ if sys != nil {
+ sys = &types.SystemContext{}
+ }
+ sys.AuthFilePath = opts.Authfile
+ sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
+
+ if opts.Username != "" && opts.Password != "" {
+ sys.DockerAuthConfig = &types.DockerAuthConfig{
+ Username: opts.Username,
+ Password: opts.Password,
+ }
+ }
+
+ options := manifests.PushOptions{
+ Store: ir.Libpod.GetStore(),
+ SystemContext: sys,
+ ImageListSelection: cp.CopySpecificImages,
+ Instances: nil,
+ RemoveSignatures: opts.RemoveSignatures,
+ SignBy: opts.SignBy,
+ ManifestType: manifestType,
+ }
+ if opts.All {
+ options.ImageListSelection = cp.CopyAllImages
+ }
+ if !opts.Quiet {
+ options.ReportWriter = os.Stderr
+ }
+ digest, err := listImage.PushManifest(dest, options)
+ if err == nil && opts.Purge {
+ _, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true)
+ }
+ if opts.DigestFile != "" {
+ if err = ioutil.WriteFile(opts.DigestFile, []byte(digest.String()), 0644); err != nil {
+ return buildahUtil.GetFailureCause(err, errors.Wrapf(err, "failed to write digest to file %q", opts.DigestFile))
+ }
+ }
+ return err
+}
diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go
new file mode 100644
index 000000000..8e3515824
--- /dev/null
+++ b/pkg/domain/infra/abi/network.go
@@ -0,0 +1,292 @@
+package abi
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "strings"
+
+ "github.com/containernetworking/cni/libcni"
+ cniversion "github.com/containernetworking/cni/pkg/version"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/network"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) {
+ var reports []*entities.NetworkListReport
+
+ config, err := ic.Libpod.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ networks, err := network.LoadCNIConfsFromDir(network.GetCNIConfDir(config))
+ if err != nil {
+ return nil, err
+ }
+
+ var tokens []string
+ // tokenize the networkListOptions.Filter in key=value.
+ if len(options.Filter) > 0 {
+ tokens = strings.Split(options.Filter, "=")
+ if len(tokens) != 2 {
+ return nil, fmt.Errorf("invalid filter syntax : %s", options.Filter)
+ }
+ }
+
+ for _, n := range networks {
+ if ifPassesFilterTest(n, tokens) {
+ reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n})
+ }
+ }
+ return reports, nil
+}
+
+func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.NetworkInspectOptions) ([]entities.NetworkInspectReport, error) {
+ var (
+ rawCNINetworks []entities.NetworkInspectReport
+ )
+
+ config, err := ic.Libpod.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, name := range namesOrIds {
+ rawList, err := network.InspectNetwork(config, name)
+ if err != nil {
+ return nil, err
+ }
+ rawCNINetworks = append(rawCNINetworks, rawList)
+ }
+ return rawCNINetworks, nil
+}
+
+func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, options entities.NetworkRmOptions) ([]*entities.NetworkRmReport, error) {
+ var reports []*entities.NetworkRmReport
+
+ config, err := ic.Libpod.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, name := range namesOrIds {
+ report := entities.NetworkRmReport{Name: name}
+ containers, err := ic.Libpod.GetAllContainers()
+ if err != nil {
+ return reports, err
+ }
+ // We need to iterate containers looking to see if they belong to the given network
+ for _, c := range containers {
+ if util.StringInSlice(name, c.Config().Networks) {
+ // if user passes force, we nuke containers
+ if !options.Force {
+ // Without the force option, we return an error
+ return reports, errors.Errorf("%q has associated containers with it. Use -f to forcibly delete containers", name)
+ }
+ if err := ic.Libpod.RemoveContainer(ctx, c, true, true); err != nil {
+ return reports, err
+ }
+ }
+ }
+ if err := network.RemoveNetwork(config, name); err != nil {
+ report.Err = err
+ }
+ reports = append(reports, &report)
+ }
+ return reports, nil
+}
+
+func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) {
+ var (
+ err error
+ fileName string
+ )
+ if len(options.MacVLAN) > 0 {
+ fileName, err = createMacVLAN(ic.Libpod, name, options)
+ } else {
+ fileName, err = createBridge(ic.Libpod, name, options)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &entities.NetworkCreateReport{Filename: fileName}, nil
+}
+
+// createBridge creates a CNI network
+func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+ isGateway := true
+ ipMasq := true
+ subnet := &options.Subnet
+ ipRange := options.Range
+ runtimeConfig, err := r.GetConfig()
+ if err != nil {
+ return "", err
+ }
+ // if range is provided, make sure it is "in" network
+ if subnet.IP != nil {
+ // if network is provided, does it conflict with existing CNI or live networks
+ err = network.ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
+ } else {
+ // if no network is provided, figure out network
+ subnet, err = network.GetFreeNetwork(runtimeConfig)
+ }
+ if err != nil {
+ return "", err
+ }
+ gateway := options.Gateway
+ if gateway == nil {
+ // if no gateway is provided, provide it as first ip of network
+ gateway = network.CalcGatewayIP(subnet)
+ }
+ // if network is provided and if gateway is provided, make sure it is "in" network
+ if options.Subnet.IP != nil && options.Gateway != nil {
+ if !subnet.Contains(gateway) {
+ return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
+ }
+ }
+ if options.Internal {
+ isGateway = false
+ ipMasq = false
+ }
+
+ // if a range is given, we need to ensure it is "in" the network range.
+ if options.Range.IP != nil {
+ if options.Subnet.IP == nil {
+ return "", errors.New("you must define a subnet range to define an ip-range")
+ }
+ firstIP, err := network.FirstIPInSubnet(&options.Range)
+ if err != nil {
+ return "", err
+ }
+ lastIP, err := network.LastIPInSubnet(&options.Range)
+ if err != nil {
+ return "", err
+ }
+ if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
+ return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String())
+ }
+ }
+ bridgeDeviceName, err := network.GetFreeDeviceName(runtimeConfig)
+ if err != nil {
+ return "", err
+ }
+
+ if len(name) > 0 {
+ netNames, err := network.GetNetworkNamesFromFileSystem(runtimeConfig)
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
+ }
+ } else {
+ // If no name is given, we give the name of the bridge device
+ name = bridgeDeviceName
+ }
+
+ ncList := network.NewNcList(name, cniversion.Current())
+ var plugins []network.CNIPlugins
+ var routes []network.IPAMRoute
+
+ defaultRoute, err := network.NewIPAMDefaultRoute()
+ if err != nil {
+ return "", err
+ }
+ routes = append(routes, defaultRoute)
+ ipamConfig, err := network.NewIPAMHostLocalConf(subnet, routes, ipRange, gateway)
+ if err != nil {
+ return "", err
+ }
+
+ // TODO need to iron out the role of isDefaultGW and IPMasq
+ bridge := network.NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
+ plugins = append(plugins, bridge)
+ plugins = append(plugins, network.NewPortMapPlugin())
+ plugins = append(plugins, network.NewFirewallPlugin())
+ // if we find the dnsname plugin, we add configuration for it
+ if network.HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS {
+ // Note: in the future we might like to allow for dynamic domain names
+ plugins = append(plugins, network.NewDNSNamePlugin(network.DefaultPodmanDomainName))
+ }
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ cniPathName := filepath.Join(network.GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
+}
+
+func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+ var (
+ plugins []network.CNIPlugins
+ )
+ liveNetNames, err := network.GetLiveNetworkNames()
+ if err != nil {
+ return "", err
+ }
+
+ config, err := r.GetConfig()
+ if err != nil {
+ return "", err
+ }
+
+ // Make sure the host-device exists
+ if !util.StringInSlice(options.MacVLAN, liveNetNames) {
+ return "", errors.Errorf("failed to find network interface %q", options.MacVLAN)
+ }
+ if len(name) > 0 {
+ netNames, err := network.GetNetworkNamesFromFileSystem(config)
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
+ }
+ } else {
+ name, err = network.GetFreeDeviceName(config)
+ if err != nil {
+ return "", err
+ }
+ }
+ ncList := network.NewNcList(name, cniversion.Current())
+ macvlan := network.NewMacVLANPlugin(options.MacVLAN)
+ plugins = append(plugins, macvlan)
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ cniPathName := filepath.Join(network.GetCNIConfDir(config), fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
+}
+
+func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool {
+ result := false
+ if len(filter) == 0 {
+ // No filter, so pass
+ return true
+ }
+ switch strings.ToLower(filter[0]) {
+ case "name":
+ if filter[1] == netconf.Name {
+ result = true
+ }
+ case "plugin":
+ plugins := network.GetCNIPlugins(netconf)
+ if strings.Contains(plugins, filter[1]) {
+ result = true
+ }
+ default:
+ result = false
+ }
+ return result
+}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
new file mode 100644
index 000000000..6d0919d2b
--- /dev/null
+++ b/pkg/domain/infra/abi/play.go
@@ -0,0 +1,554 @@
+package abi
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ ann "github.com/containers/libpod/pkg/annotations"
+ "github.com/containers/libpod/pkg/domain/entities"
+ envLib "github.com/containers/libpod/pkg/env"
+ ns "github.com/containers/libpod/pkg/namespaces"
+ createconfig "github.com/containers/libpod/pkg/spec"
+ "github.com/containers/libpod/pkg/specgen/generate"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/containers/storage"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/docker/distribution/reference"
+ "github.com/ghodss/yaml"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ v1 "k8s.io/api/core/v1"
+)
+
+const (
+ // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
+ kubeDirectoryPermission = 0755
+ // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
+ kubeFilePermission = 0644
+)
+
+func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+ var (
+ containers []*libpod.Container
+ pod *libpod.Pod
+ podOptions []libpod.PodCreateOption
+ podYAML v1.Pod
+ registryCreds *types.DockerAuthConfig
+ writer io.Writer
+ report entities.PlayKubeReport
+ )
+
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := yaml.Unmarshal(content, &podYAML); err != nil {
+ return nil, errors.Wrapf(err, "unable to read %q as YAML", path)
+ }
+
+ // NOTE: pkg/bindings/play is also parsing the file.
+ // A pkg/kube would be nice to refactor and abstract
+ // parts of the K8s-related code.
+ if podYAML.Kind != "Pod" {
+ return nil, errors.Errorf("invalid YAML kind: %q. Pod is the only supported Kubernetes YAML kind", podYAML.Kind)
+ }
+
+ // check for name collision between pod and container
+ podName := podYAML.ObjectMeta.Name
+ if podName == "" {
+ return nil, errors.Errorf("pod does not have a name")
+ }
+ for _, n := range podYAML.Spec.Containers {
+ if n.Name == podName {
+ report.Logs = append(report.Logs,
+ fmt.Sprintf("a container exists with the same name (%q) as the pod in your YAML file; changing pod name to %s_pod\n", podName, podName))
+ podName = fmt.Sprintf("%s_pod", podName)
+ }
+ }
+
+ podOptions = append(podOptions, libpod.WithInfraContainer())
+ podOptions = append(podOptions, libpod.WithPodName(podName))
+ // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml
+
+ hostname := podYAML.Spec.Hostname
+ if hostname == "" {
+ hostname = podName
+ }
+ podOptions = append(podOptions, libpod.WithPodHostname(hostname))
+
+ if podYAML.Spec.HostNetwork {
+ podOptions = append(podOptions, libpod.WithPodHostNetwork())
+ }
+
+ nsOptions, err := generate.GetNamespaceOptions(strings.Split(createconfig.DefaultKernelNamespaces, ","))
+ if err != nil {
+ return nil, err
+ }
+ podOptions = append(podOptions, nsOptions...)
+ podPorts := getPodPorts(podYAML.Spec.Containers)
+ podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts))
+
+ if options.Network != "" {
+ switch strings.ToLower(options.Network) {
+ case "bridge", "host":
+ return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML")
+ case "":
+ return nil, errors.Errorf("invalid value passed to --network: must provide a comma-separated list of CNI networks")
+ default:
+ // We'll assume this is a comma-separated list of CNI
+ // networks.
+ networks := strings.Split(options.Network, ",")
+ logrus.Debugf("Pod joining CNI networks: %v", networks)
+ podOptions = append(podOptions, libpod.WithPodNetworks(networks))
+ }
+ }
+
+ // Create the Pod
+ pod, err = ic.Libpod.NewPod(ctx, podOptions...)
+ if err != nil {
+ return nil, err
+ }
+
+ podInfraID, err := pod.InfraContainerID()
+ if err != nil {
+ return nil, err
+ }
+ hasUserns := false
+ if podInfraID != "" {
+ podCtr, err := ic.Libpod.GetContainer(podInfraID)
+ if err != nil {
+ return nil, err
+ }
+ mappings, err := podCtr.IDMappings()
+ if err != nil {
+ return nil, err
+ }
+ hasUserns = len(mappings.UIDMap) > 0
+ }
+
+ namespaces := map[string]string{
+ // Disabled during code review per mheon
+ //"pid": fmt.Sprintf("container:%s", podInfraID),
+ "net": fmt.Sprintf("container:%s", podInfraID),
+ "ipc": fmt.Sprintf("container:%s", podInfraID),
+ "uts": fmt.Sprintf("container:%s", podInfraID),
+ }
+ if hasUserns {
+ namespaces["user"] = fmt.Sprintf("container:%s", podInfraID)
+ }
+ if !options.Quiet {
+ writer = os.Stderr
+ }
+
+ if len(options.Username) > 0 && len(options.Password) > 0 {
+ registryCreds = &types.DockerAuthConfig{
+ Username: options.Username,
+ Password: options.Password,
+ }
+ }
+
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerRegistryCreds: registryCreds,
+ DockerCertPath: options.CertDir,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
+ }
+
+ // map from name to mount point
+ volumes := make(map[string]string)
+ for _, volume := range podYAML.Spec.Volumes {
+ hostPath := volume.VolumeSource.HostPath
+ if hostPath == nil {
+ return nil, errors.Errorf("HostPath is currently the only supported VolumeSource")
+ }
+ if hostPath.Type != nil {
+ switch *hostPath.Type {
+ case v1.HostPathDirectoryOrCreate:
+ if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
+ if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil {
+ return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
+ }
+ }
+ // Label a newly created volume
+ if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
+ return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
+ }
+ case v1.HostPathFileOrCreate:
+ if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
+ f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission)
+ if err != nil {
+ return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
+ }
+ if err := f.Close(); err != nil {
+ logrus.Warnf("Error in closing newly created HostPath file: %v", err)
+ }
+ }
+ // unconditionally label a newly created volume
+ if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
+ return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
+ }
+ case v1.HostPathDirectory:
+ case v1.HostPathFile:
+ case v1.HostPathUnset:
+ // do nothing here because we will verify the path exists in validateVolumeHostDir
+ break
+ default:
+ return nil, errors.Errorf("Directories are the only supported HostPath type")
+ }
+ }
+
+ if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil {
+ return nil, errors.Wrapf(err, "Error in parsing HostPath in YAML")
+ }
+ volumes[volume.Name] = hostPath.Path
+ }
+
+ seccompPaths, err := initializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, container := range podYAML.Spec.Containers {
+ pullPolicy := util.PullImageMissing
+ if len(container.ImagePullPolicy) > 0 {
+ pullPolicy, err = util.ValidatePullType(string(container.ImagePullPolicy))
+ if err != nil {
+ return nil, err
+ }
+ }
+ named, err := reference.ParseNormalizedNamed(container.Image)
+ if err != nil {
+ return nil, err
+ }
+ // In kube, if the image is tagged with latest, it should always pull
+ if tagged, isTagged := named.(reference.NamedTagged); isTagged {
+ if tagged.Tag() == image.LatestTag {
+ pullPolicy = util.PullImageAlways
+ }
+ }
+ newImage, err := ic.Libpod.ImageRuntime().New(ctx, container.Image, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullPolicy)
+ if err != nil {
+ return nil, err
+ }
+ conf, err := kubeContainerToCreateConfig(ctx, container, ic.Libpod, newImage, namespaces, volumes, pod.ID(), podInfraID, seccompPaths)
+ if err != nil {
+ return nil, err
+ }
+ ctr, err := createconfig.CreateContainerFromCreateConfig(ic.Libpod, conf, ctx, pod)
+ if err != nil {
+ return nil, err
+ }
+ containers = append(containers, ctr)
+ }
+
+ // start the containers
+ for _, ctr := range containers {
+ if err := ctr.Start(ctx, true); err != nil {
+ // Making this a hard failure here to avoid a mess
+ // the other containers are in created status
+ return nil, err
+ }
+ }
+
+ report.Pod = pod.ID()
+ for _, ctr := range containers {
+ report.Containers = append(report.Containers, ctr.ID())
+ }
+
+ return &report, nil
+}
+
+// getPodPorts converts a slice of kube container descriptions to an
+// array of ocicni portmapping descriptions usable in libpod
+func getPodPorts(containers []v1.Container) []ocicni.PortMapping {
+ var infraPorts []ocicni.PortMapping
+ for _, container := range containers {
+ for _, p := range container.Ports {
+ if p.HostPort != 0 && p.ContainerPort == 0 {
+ p.ContainerPort = p.HostPort
+ }
+ if p.Protocol == "" {
+ p.Protocol = "tcp"
+ }
+ portBinding := ocicni.PortMapping{
+ HostPort: p.HostPort,
+ ContainerPort: p.ContainerPort,
+ Protocol: strings.ToLower(string(p.Protocol)),
+ }
+ if p.HostIP != "" {
+ logrus.Debug("HostIP on port bindings is not supported")
+ }
+ // only hostPort is utilized in podman context, all container ports
+ // are accessible inside the shared network namespace
+ if p.HostPort != 0 {
+ infraPorts = append(infraPorts, portBinding)
+ }
+
+ }
+ }
+ return infraPorts
+}
+
+func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfig *createconfig.UserConfig, containerYAML v1.Container) {
+ if containerYAML.SecurityContext == nil {
+ return
+ }
+ if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil {
+ securityConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem
+ }
+ if containerYAML.SecurityContext.Privileged != nil {
+ securityConfig.Privileged = *containerYAML.SecurityContext.Privileged
+ }
+
+ if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil {
+ securityConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation
+ }
+
+ if seopt := containerYAML.SecurityContext.SELinuxOptions; seopt != nil {
+ if seopt.User != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=user:%s", seopt.User))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("user:%s", seopt.User))
+ }
+ if seopt.Role != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=role:%s", seopt.Role))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("role:%s", seopt.Role))
+ }
+ if seopt.Type != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=type:%s", seopt.Type))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("type:%s", seopt.Type))
+ }
+ if seopt.Level != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=level:%s", seopt.Level))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("level:%s", seopt.Level))
+ }
+ }
+ if caps := containerYAML.SecurityContext.Capabilities; caps != nil {
+ for _, capability := range caps.Add {
+ securityConfig.CapAdd = append(securityConfig.CapAdd, string(capability))
+ }
+ for _, capability := range caps.Drop {
+ securityConfig.CapDrop = append(securityConfig.CapDrop, string(capability))
+ }
+ }
+ if containerYAML.SecurityContext.RunAsUser != nil {
+ userConfig.User = fmt.Sprintf("%d", *containerYAML.SecurityContext.RunAsUser)
+ }
+ if containerYAML.SecurityContext.RunAsGroup != nil {
+ if userConfig.User == "" {
+ userConfig.User = "0"
+ }
+ userConfig.User = fmt.Sprintf("%s:%d", userConfig.User, *containerYAML.SecurityContext.RunAsGroup)
+ }
+}
+
+// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container
+func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
+ var (
+ containerConfig createconfig.CreateConfig
+ pidConfig createconfig.PidConfig
+ networkConfig createconfig.NetworkConfig
+ cgroupConfig createconfig.CgroupConfig
+ utsConfig createconfig.UtsConfig
+ ipcConfig createconfig.IpcConfig
+ userConfig createconfig.UserConfig
+ securityConfig createconfig.SecurityConfig
+ )
+
+ // The default for MemorySwappiness is -1, not 0
+ containerConfig.Resources.MemorySwappiness = -1
+
+ containerConfig.Image = containerYAML.Image
+ containerConfig.ImageID = newImage.ID()
+ containerConfig.Name = containerYAML.Name
+ containerConfig.Tty = containerYAML.TTY
+
+ containerConfig.Pod = podID
+
+ imageData, _ := newImage.Inspect(ctx)
+
+ userConfig.User = "0"
+ if imageData != nil {
+ userConfig.User = imageData.Config.User
+ }
+
+ setupSecurityContext(&securityConfig, &userConfig, containerYAML)
+
+ securityConfig.SeccompProfilePath = seccompPaths.findForContainer(containerConfig.Name)
+
+ containerConfig.Command = []string{}
+ if imageData != nil && imageData.Config != nil {
+ containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...)
+ }
+ if len(containerYAML.Command) != 0 {
+ containerConfig.Command = append(containerConfig.Command, containerYAML.Command...)
+ } else if imageData != nil && imageData.Config != nil {
+ containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...)
+ }
+ if imageData != nil && len(containerConfig.Command) == 0 {
+ return nil, errors.Errorf("No command specified in container YAML or as CMD or ENTRYPOINT in this image for %s", containerConfig.Name)
+ }
+
+ containerConfig.UserCommand = containerConfig.Command
+
+ containerConfig.StopSignal = 15
+
+ containerConfig.WorkDir = "/"
+ if imageData != nil {
+ // FIXME,
+ // we are currently ignoring imageData.Config.ExposedPorts
+ containerConfig.BuiltinImgVolumes = imageData.Config.Volumes
+ if imageData.Config.WorkingDir != "" {
+ containerConfig.WorkDir = imageData.Config.WorkingDir
+ }
+ containerConfig.Labels = imageData.Config.Labels
+ if imageData.Config.StopSignal != "" {
+ stopSignal, err := util.ParseSignal(imageData.Config.StopSignal)
+ if err != nil {
+ return nil, err
+ }
+ containerConfig.StopSignal = stopSignal
+ }
+ }
+
+ if containerYAML.WorkingDir != "" {
+ containerConfig.WorkDir = containerYAML.WorkingDir
+ }
+ // If the user does not pass in ID mappings, just set to basics
+ if userConfig.IDMappings == nil {
+ userConfig.IDMappings = &storage.IDMappingOptions{}
+ }
+
+ networkConfig.NetMode = ns.NetworkMode(namespaces["net"])
+ ipcConfig.IpcMode = ns.IpcMode(namespaces["ipc"])
+ utsConfig.UtsMode = ns.UTSMode(namespaces["uts"])
+ // disabled in code review per mheon
+ //containerConfig.PidMode = ns.PidMode(namespaces["pid"])
+ userConfig.UsernsMode = ns.UsernsMode(namespaces["user"])
+ if len(containerConfig.WorkDir) == 0 {
+ containerConfig.WorkDir = "/"
+ }
+
+ containerConfig.Pid = pidConfig
+ containerConfig.Network = networkConfig
+ containerConfig.Uts = utsConfig
+ containerConfig.Ipc = ipcConfig
+ containerConfig.Cgroup = cgroupConfig
+ containerConfig.User = userConfig
+ containerConfig.Security = securityConfig
+
+ annotations := make(map[string]string)
+ if infraID != "" {
+ annotations[ann.SandboxID] = infraID
+ annotations[ann.ContainerType] = ann.ContainerTypeContainer
+ }
+ containerConfig.Annotations = annotations
+
+ // Environment Variables
+ envs := map[string]string{}
+ if imageData != nil {
+ imageEnv, err := envLib.ParseSlice(imageData.Config.Env)
+ if err != nil {
+ return nil, errors.Wrap(err, "error parsing image environment variables")
+ }
+ envs = imageEnv
+ }
+ for _, e := range containerYAML.Env {
+ envs[e.Name] = e.Value
+ }
+ containerConfig.Env = envs
+
+ for _, volume := range containerYAML.VolumeMounts {
+ hostPath, exists := volumes[volume.Name]
+ if !exists {
+ return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
+ }
+ if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
+ return nil, errors.Wrapf(err, "error in parsing MountPath")
+ }
+ containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", hostPath, volume.MountPath))
+ }
+ return &containerConfig, nil
+}
+
+// kubeSeccompPaths holds information about a pod YAML's seccomp configuration
+// it holds both container and pod seccomp paths
+type kubeSeccompPaths struct {
+ containerPaths map[string]string
+ podPath string
+}
+
+// findForContainer checks whether a container has a seccomp path configured for it
+// if not, it returns the podPath, which should always have a value
+func (k *kubeSeccompPaths) findForContainer(ctrName string) string {
+ if path, ok := k.containerPaths[ctrName]; ok {
+ return path
+ }
+ return k.podPath
+}
+
+// initializeSeccompPaths takes annotations from the pod object metadata and finds annotations pertaining to seccomp
+// it parses both pod and container level
+// if the annotation is of the form "localhost/%s", the seccomp profile will be set to profileRoot/%s
+func initializeSeccompPaths(annotations map[string]string, profileRoot string) (*kubeSeccompPaths, error) {
+ seccompPaths := &kubeSeccompPaths{containerPaths: make(map[string]string)}
+ var err error
+ if annotations != nil {
+ for annKeyValue, seccomp := range annotations {
+ // check if it is prefaced with container.seccomp.security.alpha.kubernetes.io/
+ prefixAndCtr := strings.Split(annKeyValue, "/")
+ if prefixAndCtr[0]+"/" != v1.SeccompContainerAnnotationKeyPrefix {
+ continue
+ } else if len(prefixAndCtr) != 2 {
+ // this could be caused by a user inputting either of
+ // container.seccomp.security.alpha.kubernetes.io{,/}
+ // both of which are invalid
+ return nil, errors.Errorf("Invalid seccomp path: %s", prefixAndCtr[0])
+ }
+
+ path, err := verifySeccompPath(seccomp, profileRoot)
+ if err != nil {
+ return nil, err
+ }
+ seccompPaths.containerPaths[prefixAndCtr[1]] = path
+ }
+
+ podSeccomp, ok := annotations[v1.SeccompPodAnnotationKey]
+ if ok {
+ seccompPaths.podPath, err = verifySeccompPath(podSeccomp, profileRoot)
+ } else {
+ seccompPaths.podPath, err = libpod.DefaultSeccompPath()
+ }
+ if err != nil {
+ return nil, err
+ }
+ }
+ return seccompPaths, nil
+}
+
+// verifySeccompPath takes a path and checks whether it is a default, unconfined, or a path
+// the available options are parsed as defined in https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp
+func verifySeccompPath(path string, profileRoot string) (string, error) {
+ switch path {
+ case v1.DeprecatedSeccompProfileDockerDefault:
+ fallthrough
+ case v1.SeccompProfileRuntimeDefault:
+ return libpod.DefaultSeccompPath()
+ case "unconfined":
+ return path, nil
+ default:
+ parts := strings.Split(path, "/")
+ if parts[0] == "localhost" {
+ return filepath.Join(profileRoot, parts[1]), nil
+ }
+ return "", errors.Errorf("invalid seccomp path: %s", path)
+ }
+}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index b286bcf0d..16c222cbd 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -243,6 +243,10 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
}
func (ic *ContainerEngine) PodPrune(ctx context.Context, options entities.PodPruneOptions) ([]*entities.PodPruneReport, error) {
+ return ic.prunePodHelper(ctx)
+}
+
+func (ic *ContainerEngine) prunePodHelper(ctx context.Context) ([]*entities.PodPruneReport, error) {
var (
reports []*entities.PodPruneReport
)
diff --git a/pkg/domain/infra/abi/pods_stats.go b/pkg/domain/infra/abi/pods_stats.go
index a41c01da0..c6befcf95 100644
--- a/pkg/domain/infra/abi/pods_stats.go
+++ b/pkg/domain/infra/abi/pods_stats.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/utils"
"github.com/docker/go-units"
"github.com/pkg/errors"
)
@@ -68,7 +69,7 @@ func combineHumanValues(a, b uint64) string {
}
func floatToPercentString(f float64) string {
- strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f)
+ strippedFloat, err := utils.RemoveScientificNotationFromFloat(f)
if err != nil || strippedFloat == 0 {
// If things go bazinga, return a safe value
return "--"
diff --git a/pkg/domain/infra/abi/runtime.go b/pkg/domain/infra/abi/runtime.go
index fba422d8e..b9020e9a5 100644
--- a/pkg/domain/infra/abi/runtime.go
+++ b/pkg/domain/infra/abi/runtime.go
@@ -16,4 +16,9 @@ type ContainerEngine struct {
Libpod *libpod.Runtime
}
+// Container-related runtime linked against libpod library
+type SystemEngine struct {
+ Libpod *libpod.Runtime
+}
+
var shutdownSync sync.Once
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index e5c109ee6..52dfaba7d 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -5,6 +5,8 @@ import (
"fmt"
"io/ioutil"
"os"
+ "os/exec"
+ "path/filepath"
"strconv"
"syscall"
@@ -14,54 +16,18 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/util"
- iopodman "github.com/containers/libpod/pkg/varlink"
- iopodmanAPI "github.com/containers/libpod/pkg/varlinkapi"
"github.com/containers/libpod/utils"
- "github.com/containers/libpod/version"
+ "github.com/docker/distribution/reference"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
- "github.com/varlink/go/varlink"
+ "github.com/spf13/pflag"
)
func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
return ic.Libpod.Info()
}
-func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error {
- var varlinkInterfaces = []*iopodman.VarlinkInterface{
- iopodmanAPI.New(opts.Command, ic.Libpod),
- }
-
- service, err := varlink.NewService(
- "Atomic",
- "podman",
- version.Version,
- "https://github.com/containers/libpod",
- )
- if err != nil {
- return errors.Wrapf(err, "unable to create new varlink service")
- }
-
- for _, i := range varlinkInterfaces {
- if err := service.RegisterInterface(i); err != nil {
- return errors.Errorf("unable to register varlink interface %v", i)
- }
- }
-
- // Run the varlink server at the given address
- if err = service.Listen(opts.URI, opts.Timeout); err != nil {
- switch err.(type) {
- case varlink.ServiceTimeoutError:
- logrus.Infof("varlink service expired (use --timeout to increase session time beyond %s ms, 0 means never timeout)", opts.Timeout.String())
- return nil
- default:
- return errors.Wrapf(err, "unable to start varlink service")
- }
- }
- return nil
-}
-
func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error {
// do it only after podman has already re-execed and running with uid==0.
if os.Geteuid() == 0 {
@@ -83,6 +49,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
}
}
}
+ return nil
}
pausePidPath, err := util.GetRootlessPauseProcessPidPath()
@@ -175,3 +142,240 @@ func setUMask() { // nolint:deadcode,unused
func checkInput() error { // nolint:deadcode,unused
return nil
}
+
+// SystemPrune removes unsed data from the system. Pruning pods, containers, volumes and images.
+func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
+ var systemPruneReport = new(entities.SystemPruneReport)
+ podPruneReport, err := ic.prunePodHelper(ctx)
+ if err != nil {
+ return nil, err
+ }
+ systemPruneReport.PodPruneReport = podPruneReport
+
+ containerPruneReport, err := ic.pruneContainersHelper(ctx, nil)
+ if err != nil {
+ return nil, err
+ }
+ systemPruneReport.ContainerPruneReport = containerPruneReport
+
+ results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, nil)
+ if err != nil {
+ return nil, err
+ }
+ report := entities.ImagePruneReport{
+ Report: entities.Report{
+ Id: results,
+ Err: nil,
+ },
+ }
+
+ systemPruneReport.ImagePruneReport = &report
+
+ if options.Volume {
+ volumePruneReport, err := ic.pruneVolumesHelper(ctx)
+ if err != nil {
+ return nil, err
+ }
+ systemPruneReport.VolumePruneReport = volumePruneReport
+ }
+ return systemPruneReport, nil
+}
+
+func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.SystemDfOptions) (*entities.SystemDfReport, error) {
+ var (
+ dfImages []*entities.SystemDfImageReport
+ dfContainers []*entities.SystemDfContainerReport
+ dfVolumes []*entities.SystemDfVolumeReport
+ runningContainers []string
+ )
+
+ // Get Images and iterate them
+ imgs, err := ic.Libpod.ImageRuntime().GetImages()
+ if err != nil {
+ return nil, err
+ }
+ for _, i := range imgs {
+ var sharedSize uint64
+ cons, err := i.Containers()
+ if err != nil {
+ return nil, err
+ }
+ imageSize, err := i.Size(ctx)
+ if err != nil {
+ return nil, err
+ }
+ uniqueSize := *imageSize
+
+ parent, err := i.GetParent(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if parent != nil {
+ parentSize, err := parent.Size(ctx)
+ if err != nil {
+ return nil, err
+ }
+ uniqueSize = *parentSize - *imageSize
+ sharedSize = *imageSize - uniqueSize
+ }
+ var name, repository, tag string
+ for _, n := range i.Names() {
+ if len(n) > 0 {
+ name = n
+ break
+ }
+ }
+
+ named, err := reference.ParseNormalizedNamed(name)
+ if err != nil {
+ return nil, err
+ }
+ repository = named.Name()
+ if tagged, isTagged := named.(reference.NamedTagged); isTagged {
+ tag = tagged.Tag()
+ }
+
+ report := entities.SystemDfImageReport{
+ Repository: repository,
+ Tag: tag,
+ ImageID: i.ID(),
+ Created: i.Created(),
+ Size: int64(*imageSize),
+ SharedSize: int64(sharedSize),
+ UniqueSize: int64(uniqueSize),
+ Containers: len(cons),
+ }
+ dfImages = append(dfImages, &report)
+ }
+
+ // GetContainers and iterate them
+ cons, err := ic.Libpod.GetAllContainers()
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range cons {
+ iid, _ := c.Image()
+ conSize, err := c.RootFsSize()
+ if err != nil {
+ return nil, err
+ }
+ state, err := c.State()
+ if err != nil {
+ return nil, err
+ }
+ rwsize, err := c.RWSize()
+ if err != nil {
+ return nil, err
+ }
+ report := entities.SystemDfContainerReport{
+ ContainerID: c.ID(),
+ Image: iid,
+ Command: c.Command(),
+ LocalVolumes: len(c.UserVolumes()),
+ RWSize: rwsize,
+ Size: conSize,
+ Created: c.CreatedTime(),
+ Status: state.String(),
+ Names: c.Name(),
+ }
+ dfContainers = append(dfContainers, &report)
+ }
+
+ // Get volumes and iterate them
+ vols, err := ic.Libpod.GetAllVolumes()
+ if err != nil {
+ return nil, err
+ }
+
+ running, err := ic.Libpod.GetRunningContainers()
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range running {
+ runningContainers = append(runningContainers, c.ID())
+ }
+
+ for _, v := range vols {
+ var consInUse int
+ volSize, err := sizeOfPath(v.MountPoint())
+ if err != nil {
+ return nil, err
+ }
+ inUse, err := v.VolumesInUse()
+ if err != nil {
+ return nil, err
+ }
+ for _, viu := range inUse {
+ if util.StringInSlice(viu, runningContainers) {
+ consInUse += 1
+ }
+ }
+ report := entities.SystemDfVolumeReport{
+ VolumeName: v.Name(),
+ Links: consInUse,
+ Size: volSize,
+ }
+ dfVolumes = append(dfVolumes, &report)
+ }
+ return &entities.SystemDfReport{
+ Images: dfImages,
+ Containers: dfContainers,
+ Volumes: dfVolumes,
+ }, nil
+}
+
+// sizeOfPath determines the file usage of a given path. it was called volumeSize in v1
+// and now is made to be generic and take a path instead of a libpod volume
+func sizeOfPath(path string) (int64, error) {
+ var size int64
+ err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
+ if err == nil && !info.IsDir() {
+ size += info.Size()
+ }
+ return err
+ })
+ return size, err
+}
+
+func (se *SystemEngine) Reset(ctx context.Context) error {
+ return se.Libpod.Reset(ctx)
+}
+
+func (se *SystemEngine) Renumber(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig) error {
+ return nil
+}
+
+func (s SystemEngine) Migrate(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig, options entities.SystemMigrateOptions) error {
+ return nil
+}
+
+func (s SystemEngine) Shutdown(ctx context.Context) {
+ if err := s.Libpod.Shutdown(false); err != nil {
+ logrus.Error(err)
+ }
+}
+
+func unshareEnv(graphroot, runroot string) []string {
+ return append(os.Environ(), "_CONTAINERS_USERNS_CONFIGURED=done",
+ fmt.Sprintf("CONTAINERS_GRAPHROOT=%s", graphroot),
+ fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot))
+}
+
+func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {
+ var report entities.SystemVersionReport
+ v, err := define.GetVersion()
+ if err != nil {
+ return nil, err
+ }
+ report.Client = &v
+ return &report, err
+}
diff --git a/pkg/domain/infra/abi/system_novalink.go b/pkg/domain/infra/abi/system_novalink.go
new file mode 100644
index 000000000..a71b0170a
--- /dev/null
+++ b/pkg/domain/infra/abi/system_novalink.go
@@ -0,0 +1,14 @@
+// +build !varlink
+
+package abi
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error {
+ return errors.Errorf("varlink is not supported")
+}
diff --git a/pkg/domain/infra/abi/system_varlink.go b/pkg/domain/infra/abi/system_varlink.go
new file mode 100644
index 000000000..4dc766f52
--- /dev/null
+++ b/pkg/domain/infra/abi/system_varlink.go
@@ -0,0 +1,49 @@
+// +build varlink
+
+package abi
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ iopodman "github.com/containers/libpod/pkg/varlink"
+ iopodmanAPI "github.com/containers/libpod/pkg/varlinkapi"
+ "github.com/containers/libpod/version"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/varlink/go/varlink"
+)
+
+func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error {
+ var varlinkInterfaces = []*iopodman.VarlinkInterface{
+ iopodmanAPI.New(opts.Command, ic.Libpod),
+ }
+
+ service, err := varlink.NewService(
+ "Atomic",
+ "podman",
+ version.Version,
+ "https://github.com/containers/libpod",
+ )
+ if err != nil {
+ return errors.Wrapf(err, "unable to create new varlink service")
+ }
+
+ for _, i := range varlinkInterfaces {
+ if err := service.RegisterInterface(i); err != nil {
+ return errors.Errorf("unable to register varlink interface %v", i)
+ }
+ }
+
+ // Run the varlink server at the given address
+ if err = service.Listen(opts.URI, opts.Timeout); err != nil {
+ switch err.(type) {
+ case varlink.ServiceTimeoutError:
+ logrus.Infof("varlink service expired (use --time to increase session time beyond %s ms, 0 means never timeout)", opts.Timeout.String())
+ return nil
+ default:
+ return errors.Wrapf(err, "unable to start varlink service")
+ }
+ }
+ return nil
+}
diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go
index 15701342f..8d9cdde03 100644
--- a/pkg/domain/infra/abi/terminal/terminal_linux.go
+++ b/pkg/domain/infra/abi/terminal/terminal_linux.go
@@ -15,13 +15,13 @@ 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 *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) {
+func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpod.ExecConfig, streams *define.AttachStreams) (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
// events, and set the terminal to raw mode
- if haveTerminal && tty {
+ if haveTerminal && execConfig.Terminal {
cancel, oldTermState, err := handleTerminalAttach(ctx, resize)
if err != nil {
return -1, err
@@ -34,16 +34,6 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged b
}()
}
- 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)
}
diff --git a/pkg/domain/infra/abi/trust.go b/pkg/domain/infra/abi/trust.go
new file mode 100644
index 000000000..5b89c91d9
--- /dev/null
+++ b/pkg/domain/infra/abi/trust.go
@@ -0,0 +1,171 @@
+package abi
+
+import (
+ "context"
+ "encoding/json"
+ "io/ioutil"
+ "os"
+ "strings"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/trust"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func (ir *ImageEngine) ShowTrust(ctx context.Context, args []string, options entities.ShowTrustOptions) (*entities.ShowTrustReport, error) {
+ var (
+ err error
+ report entities.ShowTrustReport
+ )
+ policyPath := trust.DefaultPolicyPath(ir.Libpod.SystemContext())
+ if len(options.PolicyPath) > 0 {
+ policyPath = options.PolicyPath
+ }
+ report.Raw, err = ioutil.ReadFile(policyPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to read %s", policyPath)
+ }
+ if options.Raw {
+ return &report, nil
+ }
+ report.SystemRegistriesDirPath = trust.RegistriesDirPath(ir.Libpod.SystemContext())
+ if len(options.RegistryPath) > 0 {
+ report.SystemRegistriesDirPath = options.RegistryPath
+ }
+ policyContentStruct, err := trust.GetPolicy(policyPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "could not read trust policies")
+ }
+ report.Policies, err = getPolicyShowOutput(policyContentStruct, report.SystemRegistriesDirPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "could not show trust policies")
+ }
+ return &report, nil
+}
+
+func (ir *ImageEngine) SetTrust(ctx context.Context, args []string, options entities.SetTrustOptions) error {
+ var (
+ policyContentStruct trust.PolicyContent
+ newReposContent []trust.RepoContent
+ )
+ trustType := options.Type
+ if trustType == "accept" {
+ trustType = "insecureAcceptAnything"
+ }
+
+ pubkeysfile := options.PubKeysFile
+ if len(pubkeysfile) == 0 && trustType == "signedBy" {
+ return errors.Errorf("At least one public key must be defined for type 'signedBy'")
+ }
+
+ policyPath := trust.DefaultPolicyPath(ir.Libpod.SystemContext())
+ if len(options.PolicyPath) > 0 {
+ policyPath = options.PolicyPath
+ }
+ _, err := os.Stat(policyPath)
+ if !os.IsNotExist(err) {
+ policyContent, err := ioutil.ReadFile(policyPath)
+ if err != nil {
+ return errors.Wrapf(err, "unable to read %s", policyPath)
+ }
+ if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
+ return errors.Errorf("could not read trust policies")
+ }
+ }
+ if len(pubkeysfile) != 0 {
+ for _, filepath := range pubkeysfile {
+ newReposContent = append(newReposContent, trust.RepoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath})
+ }
+ } else {
+ newReposContent = append(newReposContent, trust.RepoContent{Type: trustType})
+ }
+ if args[0] == "default" {
+ policyContentStruct.Default = newReposContent
+ } else {
+ if len(policyContentStruct.Default) == 0 {
+ return errors.Errorf("Default trust policy must be set.")
+ }
+ registryExists := false
+ for transport, transportval := range policyContentStruct.Transports {
+ _, registryExists = transportval[args[0]]
+ if registryExists {
+ policyContentStruct.Transports[transport][args[0]] = newReposContent
+ break
+ }
+ }
+ if !registryExists {
+ if policyContentStruct.Transports == nil {
+ policyContentStruct.Transports = make(map[string]trust.RepoMap)
+ }
+ if policyContentStruct.Transports["docker"] == nil {
+ policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent)
+ }
+ policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...)
+ }
+ }
+
+ data, err := json.MarshalIndent(policyContentStruct, "", " ")
+ if err != nil {
+ return errors.Wrapf(err, "error setting trust policy")
+ }
+ return ioutil.WriteFile(policyPath, data, 0644)
+}
+
+func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]*trust.TrustPolicy, error) {
+ var output []*trust.TrustPolicy
+
+ registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(policyContentStruct.Default) > 0 {
+ defaultPolicyStruct := trust.TrustPolicy{
+ Name: "* (default)",
+ RepoName: "default",
+ Type: trustTypeDescription(policyContentStruct.Default[0].Type),
+ }
+ output = append(output, &defaultPolicyStruct)
+ }
+ for _, transval := range policyContentStruct.Transports {
+ for repo, repoval := range transval {
+ tempTrustShowOutput := trust.TrustPolicy{
+ Name: repo,
+ RepoName: repo,
+ Type: repoval[0].Type,
+ }
+ // TODO - keyarr is not used and I don't know its intent; commenting out for now for someone to fix later
+ //keyarr := []string{}
+ uids := []string{}
+ for _, repoele := range repoval {
+ if len(repoele.KeyPath) > 0 {
+ //keyarr = append(keyarr, repoele.KeyPath)
+ uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...)
+ }
+ if len(repoele.KeyData) > 0 {
+ //keyarr = append(keyarr, string(repoele.KeyData))
+ uids = append(uids, trust.GetGPGIdFromKeyData(repoele.KeyData)...)
+ }
+ }
+ tempTrustShowOutput.GPGId = strings.Join(uids, ", ")
+
+ registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs)
+ if registryNamespace != nil {
+ tempTrustShowOutput.SignatureStore = registryNamespace.SigStore
+ }
+ output = append(output, &tempTrustShowOutput)
+ }
+ }
+ return output, nil
+}
+
+var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"}
+
+func trustTypeDescription(trustType string) string {
+ trustDescription, exist := typeDescription[trustType]
+ if !exist {
+ logrus.Warnf("invalid trust type %s", trustType)
+ }
+ return trustDescription
+}
diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go
index bdae4359d..91b2440df 100644
--- a/pkg/domain/infra/abi/volumes.go
+++ b/pkg/domain/infra/abi/volumes.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package abi
import (
@@ -113,6 +111,10 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
}
func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
+ return ic.pruneVolumesHelper(ctx)
+}
+
+func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context) ([]*entities.VolumePruneReport, error) {
var (
reports []*entities.VolumePruneReport
)
diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go
index 7aa6986a7..67c1cd534 100644
--- a/pkg/domain/infra/runtime_abi.go
+++ b/pkg/domain/infra/runtime_abi.go
@@ -6,8 +6,10 @@ import (
"context"
"fmt"
+ "github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/containers/libpod/pkg/domain/infra/tunnel"
)
@@ -36,3 +38,32 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
}
+
+// NewSystemEngine factory provides a libpod runtime for specialized system operations
+func NewSystemEngine(setup entities.EngineSetup, facts *entities.PodmanConfig) (entities.SystemEngine, error) {
+ switch facts.EngineMode {
+ case entities.ABIMode:
+ var r *libpod.Runtime
+ var err error
+ switch setup {
+ case entities.NormalMode:
+ r, err = GetRuntime(context.Background(), facts.FlagSet, facts)
+ case entities.RenumberMode:
+ r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts)
+ case entities.ResetMode:
+ r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts)
+ case entities.MigrateMode:
+ name, flagErr := facts.FlagSet.GetString("new-runtime")
+ if flagErr != nil {
+ return nil, flagErr
+ }
+ r, err = GetRuntimeMigrate(context.Background(), facts.FlagSet, facts, name)
+ case entities.NoFDsMode:
+ r, err = GetRuntimeDisableFDs(context.Background(), facts.FlagSet, facts)
+ }
+ return &abi.SystemEngine{Libpod: r}, err
+ case entities.TunnelMode:
+ return nil, fmt.Errorf("tunnel system runtime not supported")
+ }
+ return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
+}
diff --git a/pkg/domain/infra/runtime_abi_unsupported.go b/pkg/domain/infra/runtime_abi_unsupported.go
new file mode 100644
index 000000000..c4e25e990
--- /dev/null
+++ b/pkg/domain/infra/runtime_abi_unsupported.go
@@ -0,0 +1,14 @@
+// +build !ABISupport
+
+package infra
+
+import (
+ "errors"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+// NewSystemEngine factory provides a libpod runtime for specialized system operations
+func NewSystemEngine(setup entities.EngineSetup, facts *entities.PodmanConfig) (entities.SystemEngine, error) {
+ return nil, errors.New("not implemented")
+}
diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go
deleted file mode 100644
index ea5d0e6f2..000000000
--- a/pkg/domain/infra/runtime_image_proxy.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// +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 podman is separated from libpod repo
-
-func NewLibpodImageRuntime(flags *pflag.FlagSet, opts *entities.PodmanConfig) (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
index 7c9180d43..a57eadc63 100644
--- a/pkg/domain/infra/runtime_libpod.go
+++ b/pkg/domain/infra/runtime_libpod.go
@@ -213,6 +213,9 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
if fs.Changed("hooks-dir") {
options = append(options, libpod.WithHooksDir(cfg.Engine.HooksDir...))
}
+ if fs.Changed("registries-conf") {
+ options = append(options, libpod.WithRegistriesConf(cfg.RegistriesConf))
+ }
// TODO flag to set CNI plugins dir?
diff --git a/pkg/domain/infra/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go
index 41193fd89..e7002e20f 100644
--- a/pkg/domain/infra/runtime_proxy.go
+++ b/pkg/domain/infra/runtime_proxy.go
@@ -19,3 +19,11 @@ func NewLibpodRuntime(flags *flag.FlagSet, opts *entities.PodmanConfig) (entitie
}
return &abi.ContainerEngine{Libpod: r}, nil
}
+
+func NewLibpodImageRuntime(flags *flag.FlagSet, opts *entities.PodmanConfig) (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/tunnel/auto-update.go b/pkg/domain/infra/tunnel/auto-update.go
new file mode 100644
index 000000000..5c2dd360d
--- /dev/null
+++ b/pkg/domain/infra/tunnel/auto-update.go
@@ -0,0 +1,12 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) AutoUpdate(ctx context.Context, options entities.AutoUpdateOptions) (*entities.AutoUpdateReport, []error) {
+ return nil, []error{errors.New("not implemented")}
+}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 32f9c4e36..beba55c2b 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -4,16 +4,25 @@ import (
"context"
"io"
"os"
+ "strconv"
+ "strings"
+ "time"
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/bindings/containers"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
+func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, image string, args []string, options entities.ContainerRunlabelOptions) error {
+ return errors.New("not implemented")
+}
+
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
@@ -80,10 +89,25 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
}
for _, c := range ctrs {
report := entities.StopReport{Id: c.ID}
- report.Err = containers.Stop(ic.ClientCxt, c.ID, &options.Timeout)
- // TODO we need to associate errors returned by http with common
- // define.errors so that we can equity tests. this will allow output
- // to be the same as the native client
+ if err = containers.Stop(ic.ClientCxt, c.ID, &options.Timeout); err != nil {
+ // These first two are considered non-fatal under the right conditions
+ if errors.Cause(err).Error() == define.ErrCtrStopped.Error() {
+ logrus.Debugf("Container %s is already stopped", c.ID)
+ reports = append(reports, &report)
+ continue
+ } else if options.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() {
+ logrus.Debugf("Container %s is not running, could not stop", c.ID)
+ reports = append(reports, &report)
+ continue
+ }
+
+ // TODO we need to associate errors returned by http with common
+ // define.errors so that we can equity tests. this will allow output
+ // to be the same as the native client
+ report.Err = err
+ reports = append(reports, &report)
+ continue
+ }
reports = append(reports, &report)
}
return reports, nil
@@ -263,7 +287,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
}
}
for _, c := range ctrs {
- report, err := containers.Checkpoint(ic.ClientCxt, c.ID, &options.Keep, &options.LeaveRuninng, &options.TCPEstablished, &options.IgnoreRootFS, &options.Export)
+ report, err := containers.Checkpoint(ic.ClientCxt, c.ID, &options.Keep, &options.LeaveRunning, &options.TCPEstablished, &options.IgnoreRootFS, &options.Export)
if err != nil {
reports = append(reports, &entities.CheckpointReport{Id: c.ID, Err: err})
}
@@ -314,21 +338,92 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
return &entities.ContainerCreateReport{Id: response.ID}, nil
}
-func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
- // The endpoint is not ready yet and requires some more work.
- return errors.New("not implemented yet")
+func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIds []string, options entities.ContainerLogsOptions) error {
+ since := options.Since.Format(time.RFC3339)
+ tail := strconv.FormatInt(options.Tail, 10)
+ stdout := options.Writer != nil
+ opts := containers.LogOptions{
+ Follow: &options.Follow,
+ Since: &since,
+ Stderr: &stdout,
+ Stdout: &stdout,
+ Tail: &tail,
+ Timestamps: &options.Timestamps,
+ Until: nil,
+ }
+
+ var err error
+ outCh := make(chan string)
+ ctx, cancel := context.WithCancel(context.Background())
+ go func() {
+ err = containers.Logs(ic.ClientCxt, nameOrIds[0], opts, outCh, outCh)
+ cancel()
+ }()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return err
+ case line := <-outCh:
+ _, _ = io.WriteString(options.Writer, line)
+ }
+ }
}
func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error {
- return errors.New("not implemented")
+ return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr, nil)
}
-func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) {
+func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) {
return 125, errors.New("not implemented")
}
+func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (string, error) {
+ return "", errors.New("not implemented")
+}
+
+func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { //nolint
+ attachErr := make(chan error)
+ attachReady := make(chan bool)
+ go func() {
+ err := containers.Attach(ic.ClientCxt, name, detachKeys, bindings.PFalse, bindings.PTrue, input, output, errput, attachReady)
+ attachErr <- err
+ }()
+ // Wait for the attach to actually happen before starting
+ // the container.
+ <-attachReady
+ if err := containers.Start(ic.ClientCxt, name, detachKeys); err != nil {
+ return err
+ }
+ return <-attachErr
+}
+
func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) {
- return nil, errors.New("not implemented")
+ var reports []*entities.ContainerStartReport
+ for _, name := range namesOrIds {
+ report := entities.ContainerStartReport{
+ Id: name,
+ RawInput: name,
+ ExitCode: 125,
+ }
+ if options.Attach {
+ report.Err = startAndAttach(ic, name, &options.DetachKeys, options.Stdin, options.Stdout, options.Stderr)
+ if report.Err == nil {
+ exitCode, err := containers.Wait(ic.ClientCxt, name, nil)
+ if err == nil {
+ report.ExitCode = int(exitCode)
+ }
+ } else {
+ report.ExitCode = define.ExitCode(report.Err)
+ }
+ reports = append(reports, &report)
+ return reports, nil
+ }
+ report.Err = containers.Start(ic.ClientCxt, name, &options.DetachKeys)
+ report.ExitCode = define.ExitCode(report.Err)
+ reports = append(reports, &report)
+ }
+ return reports, nil
}
func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.ContainerListOptions) ([]entities.ListContainer, error) {
@@ -336,7 +431,30 @@ func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.C
}
func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) {
- return nil, errors.New("not implemented")
+ if opts.Rm {
+ logrus.Info("the remote client does not support --rm yet")
+ }
+ con, err := containers.CreateWithSpec(ic.ClientCxt, opts.Spec)
+ if err != nil {
+ return nil, err
+ }
+ report := entities.ContainerRunReport{Id: con.ID}
+ // Attach
+ if !opts.Detach {
+ err = startAndAttach(ic, con.ID, &opts.DetachKeys, opts.InputStream, opts.OutputStream, opts.ErrorStream)
+ if err == nil {
+ exitCode, err := containers.Wait(ic.ClientCxt, con.ID, nil)
+ if err == nil {
+ report.ExitCode = int(exitCode)
+ }
+ }
+ } else {
+ err = containers.Start(ic.ClientCxt, con.ID, nil)
+ }
+ if err != nil {
+ report.ExitCode = define.ExitCode(err)
+ }
+ return &report, err
}
func (ic *ContainerEngine) ContainerDiff(ctx context.Context, nameOrId string, _ entities.DiffOptions) (*entities.DiffReport, error) {
@@ -356,6 +474,11 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin
}
for _, ctr := range ctrs {
err := containers.ContainerInit(ic.ClientCxt, ctr.ID)
+ // When using all, it is NOT considered an error if a container
+ // has already been init'd.
+ if err != nil && options.All && strings.Contains(errors.Cause(err).Error(), define.ErrCtrStateInvalid.Error()) {
+ err = nil
+ }
reports = append(reports, &entities.ContainerInitReport{
Err: err,
Id: ctr.ID,
@@ -377,7 +500,29 @@ func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) {
}
func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) {
- return nil, errors.New("not implemented")
+ var (
+ reports []*entities.ContainerPortReport
+ namesOrIds []string
+ )
+ if len(nameOrId) > 0 {
+ namesOrIds = append(namesOrIds, nameOrId)
+ }
+ ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
+ if err != nil {
+ return nil, err
+ }
+ for _, con := range ctrs {
+ if con.State != define.ContainerStateRunning.String() {
+ continue
+ }
+ if len(con.Ports) > 0 {
+ reports = append(reports, &entities.ContainerPortReport{
+ Id: con.ID,
+ Ports: con.Ports,
+ })
+ }
+ }
+ return reports, nil
}
func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) {
@@ -387,3 +532,7 @@ func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string,
// Shutdown Libpod engine
func (ic *ContainerEngine) Shutdown(_ context.Context) {
}
+
+func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error {
+ return errors.New("not implemented")
+}
diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go
index 93da3aeb4..6a08a1f85 100644
--- a/pkg/domain/infra/tunnel/events.go
+++ b/pkg/domain/infra/tunnel/events.go
@@ -25,6 +25,7 @@ func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptio
for e := range binChan {
opts.EventChan <- entities.ConvertToLibpodEvent(e)
}
+ close(opts.EventChan)
}()
- return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters)
+ return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters, &opts.Stream)
}
diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go
index 3cd483053..eb5587f89 100644
--- a/pkg/domain/infra/tunnel/generate.go
+++ b/pkg/domain/infra/tunnel/generate.go
@@ -3,6 +3,7 @@ package tunnel
import (
"context"
+ "github.com/containers/libpod/pkg/bindings/generate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
)
@@ -10,3 +11,7 @@ import (
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
return nil, errors.New("not implemented for tunnel")
}
+
+func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+ return generate.GenerateKube(ic.ClientCxt, nameOrID, options)
+}
diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go
index 682d60d6a..862c7a5d6 100644
--- a/pkg/domain/infra/tunnel/helpers.go
+++ b/pkg/domain/infra/tunnel/helpers.go
@@ -20,7 +20,7 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam
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)
+ c, err := containers.List(contextWithConnection, nil, bindings.PTrue, nil, nil, nil, bindings.PTrue)
if err != nil {
return nil, err
}
@@ -37,7 +37,7 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam
}
}
if !found {
- return nil, errors.Errorf("unable to find container %q", id)
+ return nil, errors.Wrapf(define.ErrNoSuchCtr, "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
index dcc5fc3e7..c300e74d0 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -4,6 +4,8 @@ import (
"context"
"io/ioutil"
"os"
+ "path"
+ "strings"
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker/reference"
@@ -12,6 +14,7 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/utils"
utils2 "github.com/containers/libpod/utils"
+ "github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
)
@@ -20,13 +23,18 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
return &entities.BoolReport{Value: found}, err
}
-func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
- return images.Remove(ir.ClientCxt, imagesArg, opts)
+func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) {
+ return images.BatchRemove(ir.ClientCxt, imagesArg, opts)
}
func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) {
- images, err := images.List(ir.ClientCxt, &opts.All, opts.Filters)
+ filters := make(map[string][]string, len(opts.Filter))
+ for _, filter := range opts.Filter {
+ f := strings.Split(filter, "=")
+ filters[f[0]] = f[1:]
+ }
+ images, err := images.List(ir.ClientCxt, &opts.All, filters)
if err != nil {
return nil, err
}
@@ -61,7 +69,13 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entiti
}
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
- results, err := images.Prune(ir.ClientCxt, &opts.All, opts.Filters)
+ filters := make(map[string][]string, len(opts.Filter))
+ for _, filter := range opts.Filter {
+ f := strings.Split(filter, "=")
+ filters[f[0]] = f[1:]
+ }
+
+ results, err := images.Prune(ir.ClientCxt, &opts.All, filters)
if err != nil {
return nil, err
}
@@ -112,7 +126,7 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrId string, tags []string,
func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string, options entities.ImageUntagOptions) error {
// Remove all tags if none are provided
if len(tags) == 0 {
- newImage, err := images.GetImage(ir.ClientCxt, nameOrId, &bindings.PFalse)
+ newImage, err := images.GetImage(ir.ClientCxt, nameOrId, bindings.PFalse)
if err != nil {
return err
}
@@ -190,7 +204,6 @@ func (ir *ImageEngine) Save(ctx context.Context, nameOrId string, tags []string,
f *os.File
err error
)
-
switch options.Format {
case "oci-dir", "docker-dir":
f, err = ioutil.TempFile("", "podman_save")
@@ -254,13 +267,24 @@ func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
}
func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) {
- return nil, errors.New("not implemented yet")
+ if len(containerFiles) > 1 {
+ return nil, errors.New("something")
+ }
+ tarfile, err := archive.Tar(path.Base(containerFiles[0]), 0)
+ if err != nil {
+ return nil, err
+ }
+ return images.Build(ir.ClientCxt, containerFiles, opts, tarfile)
}
func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) {
- return nil, errors.New("not implemented yet")
+ return images.Tree(ir.ClientCxt, nameOrId, &opts.WhatRequires)
}
// Shutdown Libpod engine
func (ir *ImageEngine) Shutdown(_ context.Context) {
}
+
+func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) {
+ return nil, errors.New("not implemented yet")
+}
diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go
index 18b400533..beac378fe 100644
--- a/pkg/domain/infra/tunnel/manifest.go
+++ b/pkg/domain/infra/tunnel/manifest.go
@@ -3,6 +3,7 @@ package tunnel
import (
"context"
"encoding/json"
+ "fmt"
"strings"
"github.com/containers/libpod/libpod/image"
@@ -56,9 +57,29 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
}
manifestAddOpts.Annotation = annotations
}
- listID, err := manifests.Add(ctx, opts.Images[1], manifestAddOpts)
+ listID, err := manifests.Add(ir.ClientCxt, opts.Images[1], manifestAddOpts)
if err != nil {
return listID, errors.Wrapf(err, "error adding to manifest list %s", opts.Images[1])
}
return listID, nil
}
+
+// ManifestAnnotate updates an entry of the manifest list
+func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) {
+ return "", errors.New("not implemented")
+}
+
+// ManifestRemove removes the digest from manifest list
+func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) {
+ updatedListID, err := manifests.Remove(ir.ClientCxt, names[0], names[1])
+ if err != nil {
+ return updatedListID, errors.Wrapf(err, "error removing from manifest %s", names[0])
+ }
+ return fmt.Sprintf("%s :%s\n", updatedListID, names[1]), nil
+}
+
+// ManifestPush pushes a manifest list or image index to the destination
+func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts entities.ManifestPushOptions) error {
+ _, err := manifests.Push(ir.ClientCxt, names[0], &names[1], &opts.All)
+ return err
+}
diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go
new file mode 100644
index 000000000..7725d8257
--- /dev/null
+++ b/pkg/domain/infra/tunnel/network.go
@@ -0,0 +1,40 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/bindings/network"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) {
+ return network.List(ic.ClientCxt)
+}
+
+func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.NetworkInspectOptions) ([]entities.NetworkInspectReport, error) {
+ var reports []entities.NetworkInspectReport
+ for _, name := range namesOrIds {
+ report, err := network.Inspect(ic.ClientCxt, name)
+ if err != nil {
+ return nil, err
+ }
+ reports = append(reports, report...)
+ }
+ return reports, nil
+}
+
+func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, options entities.NetworkRmOptions) ([]*entities.NetworkRmReport, error) {
+ var reports []*entities.NetworkRmReport
+ for _, name := range namesOrIds {
+ report, err := network.Remove(ic.ClientCxt, name, &options.Force)
+ if err != nil {
+ report[0].Err = err
+ }
+ reports = append(reports, report...)
+ }
+ return reports, nil
+}
+
+func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) {
+ return network.Create(ic.ClientCxt, options, &name)
+}
diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go
new file mode 100644
index 000000000..15383a703
--- /dev/null
+++ b/pkg/domain/infra/tunnel/play.go
@@ -0,0 +1,12 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/bindings/play"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+ return play.PlayKube(ic.ClientCxt, path, options)
+}
diff --git a/pkg/domain/infra/tunnel/runtime.go b/pkg/domain/infra/tunnel/runtime.go
index c111f99e9..357e2c390 100644
--- a/pkg/domain/infra/tunnel/runtime.go
+++ b/pkg/domain/infra/tunnel/runtime.go
@@ -13,3 +13,8 @@ type ImageEngine struct {
type ContainerEngine struct {
ClientCxt context.Context
}
+
+// Container-related runtime using an ssh-tunnel to utilize Podman service
+type SystemEngine struct {
+ ClientCxt context.Context
+}
diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go
index 97bf885e7..109e6c1d7 100644
--- a/pkg/domain/infra/tunnel/system.go
+++ b/pkg/domain/infra/tunnel/system.go
@@ -21,3 +21,20 @@ func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceO
func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error {
panic(errors.New("rootless engine mode is not supported when tunneling"))
}
+
+// SystemPrune prunes unused data from the system.
+func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
+ return system.Prune(ic.ClientCxt, &options.All, &options.Volume)
+}
+
+func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.SystemDfOptions) (*entities.SystemDfReport, error) {
+ return system.DiskUsage(ic.ClientCxt)
+}
+
+func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error {
+ return errors.New("unshare is not supported on remote clients")
+}
+
+func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {
+ return system.Version(ic.ClientCxt)
+}
diff --git a/pkg/domain/infra/tunnel/trust.go b/pkg/domain/infra/tunnel/trust.go
new file mode 100644
index 000000000..a976bfdc2
--- /dev/null
+++ b/pkg/domain/infra/tunnel/trust.go
@@ -0,0 +1,16 @@
+package tunnel
+
+import (
+ "context"
+ "errors"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ir *ImageEngine) ShowTrust(ctx context.Context, args []string, options entities.ShowTrustOptions) (*entities.ShowTrustReport, error) {
+ return nil, errors.New("not implemented")
+}
+
+func (ir *ImageEngine) SetTrust(ctx context.Context, args []string, options entities.SetTrustOptions) error {
+ return errors.New("not implemented")
+}
diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go
index 970d47636..3117b0ca4 100644
--- a/pkg/errorhandling/errorhandling.go
+++ b/pkg/errorhandling/errorhandling.go
@@ -2,10 +2,46 @@ package errorhandling
import (
"os"
+ "strings"
+ "github.com/hashicorp/go-multierror"
+ "github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+// JoinErrors converts the error slice into a single human-readable error.
+func JoinErrors(errs []error) error {
+ if len(errs) == 0 {
+ return nil
+ }
+
+ // `multierror` appends new lines which we need to remove to prevent
+ // blank lines when printing the error.
+ var multiE *multierror.Error
+ multiE = multierror.Append(multiE, errs...)
+ return errors.New(strings.TrimSpace(multiE.ErrorOrNil().Error()))
+}
+
+// ErrorsToString converts the slice of errors into a slice of corresponding
+// error messages.
+func ErrorsToStrings(errs []error) []string {
+ strErrs := make([]string, len(errs))
+ for i := range errs {
+ strErrs[i] = errs[i].Error()
+ }
+ return strErrs
+}
+
+// StringsToErrors converts a slice of error messages into a slice of
+// corresponding errors.
+func StringsToErrors(strErrs []string) []error {
+ errs := make([]error, len(strErrs))
+ for i := range strErrs {
+ errs[i] = errors.New(strErrs[i])
+ }
+ return errs
+}
+
// SyncQuiet syncs a file and logs any error. Should only be used within
// a defer.
func SyncQuiet(f *os.File) {
diff --git a/pkg/network/devices.go b/pkg/network/devices.go
index 78e1a5aa5..8eac32142 100644
--- a/pkg/network/devices.go
+++ b/pkg/network/devices.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os/exec"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
"github.com/sirupsen/logrus"
@@ -11,12 +12,12 @@ import (
// GetFreeDeviceName returns a device name that is unused; used when no network
// name is provided by user
-func GetFreeDeviceName() (string, error) {
+func GetFreeDeviceName(config *config.Config) (string, error) {
var (
deviceNum uint
deviceName string
)
- networkNames, err := GetNetworkNamesFromFileSystem()
+ networkNames, err := GetNetworkNamesFromFileSystem(config)
if err != nil {
return "", err
}
@@ -24,7 +25,7 @@ func GetFreeDeviceName() (string, error) {
if err != nil {
return "", err
}
- bridgeNames, err := GetBridgeNamesFromFileSystem()
+ bridgeNames, err := GetBridgeNamesFromFileSystem(config)
if err != nil {
return "", err
}
diff --git a/pkg/network/files.go b/pkg/network/files.go
index 116189c43..81c0e1a28 100644
--- a/pkg/network/files.go
+++ b/pkg/network/files.go
@@ -9,9 +9,17 @@ import (
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
+ "github.com/containers/common/pkg/config"
"github.com/pkg/errors"
)
+func GetCNIConfDir(config *config.Config) string {
+ if len(config.Network.NetworkConfigDir) < 1 {
+ return CNIConfigDir
+ }
+ return config.Network.NetworkConfigDir
+}
+
// LoadCNIConfsFromDir loads all the CNI configurations from a dir
func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) {
var configs []*libcni.NetworkConfigList
@@ -33,8 +41,8 @@ func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) {
// GetCNIConfigPathByName finds a CNI network by name and
// returns its configuration file path
-func GetCNIConfigPathByName(name string) (string, error) {
- files, err := libcni.ConfFiles(CNIConfigDir, []string{".conflist"})
+func GetCNIConfigPathByName(config *config.Config, name string) (string, error) {
+ files, err := libcni.ConfFiles(GetCNIConfDir(config), []string{".conflist"})
if err != nil {
return "", err
}
@@ -52,8 +60,8 @@ func GetCNIConfigPathByName(name string) (string, error) {
// ReadRawCNIConfByName reads the raw CNI configuration for a CNI
// network by name
-func ReadRawCNIConfByName(name string) ([]byte, error) {
- confFile, err := GetCNIConfigPathByName(name)
+func ReadRawCNIConfByName(config *config.Config, name string) ([]byte, error) {
+ confFile, err := GetCNIConfigPathByName(config, name)
if err != nil {
return nil, err
}
@@ -73,9 +81,10 @@ func GetCNIPlugins(list *libcni.NetworkConfigList) string {
// GetNetworksFromFilesystem gets all the networks from the cni configuration
// files
-func GetNetworksFromFilesystem() ([]*allocator.Net, error) {
+func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) {
var cniNetworks []*allocator.Net
- networks, err := LoadCNIConfsFromDir(CNIConfigDir)
+
+ networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config))
if err != nil {
return nil, err
}
@@ -96,9 +105,10 @@ func GetNetworksFromFilesystem() ([]*allocator.Net, error) {
// GetNetworkNamesFromFileSystem gets all the names from the cni network
// configuration files
-func GetNetworkNamesFromFileSystem() ([]string, error) {
+func GetNetworkNamesFromFileSystem(config *config.Config) ([]string, error) {
var networkNames []string
- networks, err := LoadCNIConfsFromDir(CNIConfigDir)
+
+ networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config))
if err != nil {
return nil, err
}
@@ -133,9 +143,10 @@ func GetInterfaceNameFromConfig(path string) (string, error) {
// GetBridgeNamesFromFileSystem is a convenience function to get all the bridge
// names from the configured networks
-func GetBridgeNamesFromFileSystem() ([]string, error) {
+func GetBridgeNamesFromFileSystem(config *config.Config) ([]string, error) {
var bridgeNames []string
- networks, err := LoadCNIConfsFromDir(CNIConfigDir)
+
+ networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config))
if err != nil {
return nil, err
}
diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go
index 34ff00024..4271d3f54 100644
--- a/pkg/network/netconflist.go
+++ b/pkg/network/netconflist.go
@@ -21,10 +21,11 @@ func NewNcList(name, version string) NcList {
// NewHostLocalBridge creates a new LocalBridge for host-local
func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamConf IPAMHostLocalConf) *HostLocalBridge {
hostLocalBridge := HostLocalBridge{
- PluginType: "bridge",
- BrName: name,
- IPMasq: ipMasq,
- IPAM: ipamConf,
+ PluginType: "bridge",
+ BrName: name,
+ IPMasq: ipMasq,
+ HairpinMode: true,
+ IPAM: ipamConf,
}
if isGateWay {
hostLocalBridge.IsGW = true
diff --git a/pkg/network/network.go b/pkg/network/network.go
index bb6f13579..526ee92d8 100644
--- a/pkg/network/network.go
+++ b/pkg/network/network.go
@@ -7,13 +7,17 @@ import (
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+// DefaultNetworkDriver is the default network type used
+var DefaultNetworkDriver string = "bridge"
+
// SupportedNetworkDrivers describes the list of supported drivers
-var SupportedNetworkDrivers = []string{"bridge"}
+var SupportedNetworkDrivers = []string{DefaultNetworkDriver}
// IsSupportedDriver checks if the user provided driver is supported
func IsSupportedDriver(driver string) error {
@@ -56,8 +60,8 @@ func GetLiveNetworkNames() ([]string, error) {
// GetFreeNetwork looks for a free network according to existing cni configuration
// files and network interfaces.
-func GetFreeNetwork() (*net.IPNet, error) {
- networks, err := GetNetworksFromFilesystem()
+func GetFreeNetwork(config *config.Config) (*net.IPNet, error) {
+ networks, err := GetNetworksFromFilesystem(config)
if err != nil {
return nil, err
}
@@ -131,8 +135,8 @@ func networkIntersect(n1, n2 *net.IPNet) bool {
// ValidateUserNetworkIsAvailable returns via an error if a network is available
// to be used
-func ValidateUserNetworkIsAvailable(userNet *net.IPNet) error {
- networks, err := GetNetworksFromFilesystem()
+func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) error {
+ networks, err := GetNetworksFromFilesystem(config)
if err != nil {
return err
}
@@ -153,8 +157,8 @@ func ValidateUserNetworkIsAvailable(userNet *net.IPNet) error {
// RemoveNetwork removes a given network by name. If the network has container associated with it, that
// must be handled outside the context of this.
-func RemoveNetwork(name string) error {
- cniPath, err := GetCNIConfigPathByName(name)
+func RemoveNetwork(config *config.Config, name string) error {
+ cniPath, err := GetCNIConfigPathByName(config, name)
if err != nil {
return err
}
@@ -181,8 +185,8 @@ func RemoveNetwork(name string) error {
}
// InspectNetwork reads a CNI config and returns its configuration
-func InspectNetwork(name string) (map[string]interface{}, error) {
- b, err := ReadRawCNIConfByName(name)
+func InspectNetwork(config *config.Config, name string) (map[string]interface{}, error) {
+ b, err := ReadRawCNIConfByName(config, name)
if err != nil {
return nil, err
}
@@ -190,3 +194,16 @@ func InspectNetwork(name string) (map[string]interface{}, error) {
err = json.Unmarshal(b, &rawList)
return rawList, err
}
+
+// Exists says whether a given network exists or not; it meant
+// specifically for restful reponses so 404s can be used
+func Exists(config *config.Config, name string) (bool, error) {
+ _, err := ReadRawCNIConfByName(config, name)
+ if err != nil {
+ if errors.Cause(err) == ErrNetworkNotFound {
+ return false, nil
+ }
+ return false, err
+ }
+ return true, nil
+}
diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go
index 907063df9..ec96367cb 100644
--- a/pkg/ps/ps.go
+++ b/pkg/ps/ps.go
@@ -23,7 +23,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
filterFuncs []libpod.ContainerFilter
pss []entities.ListContainer
)
- all := options.All
+ all := options.All || options.Last > 0
if len(options.Filters) > 0 {
for k, v := range options.Filters {
for _, val := range v {
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index 72d461cdc..716db81dc 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -535,32 +535,30 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
}
}
-static void
-join_namespace_or_die (int pid_to_join, const char *ns_file)
+static int
+open_namespace (int pid_to_join, const char *ns_file)
{
char ns_path[PATH_MAX];
int ret;
- int fd;
ret = snprintf (ns_path, PATH_MAX, "/proc/%d/ns/%s", pid_to_join, ns_file);
if (ret == PATH_MAX)
{
fprintf (stderr, "internal error: namespace path too long\n");
- _exit (EXIT_FAILURE);
+ return -1;
}
- fd = open (ns_path, O_CLOEXEC | O_RDONLY);
- if (fd < 0)
- {
- fprintf (stderr, "cannot open: %s\n", ns_path);
- _exit (EXIT_FAILURE);
- }
- if (setns (fd, 0) < 0)
+ return open (ns_path, O_CLOEXEC | O_RDONLY);
+}
+
+static void
+join_namespace_or_die (const char *name, int ns_fd)
+{
+ if (setns (ns_fd, 0) < 0)
{
- fprintf (stderr, "cannot set namespace to %s: %s\n", ns_path, strerror (errno));
+ fprintf (stderr, "cannot set %s namespace\n", name);
_exit (EXIT_FAILURE);
}
- close (fd);
}
int
@@ -570,6 +568,8 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path)
char gid[16];
char **argv;
int pid;
+ int mnt_ns = -1;
+ int user_ns = -1;
char *cwd = getcwd (NULL, 0);
sigset_t sigset, oldsigset;
@@ -589,14 +589,28 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path)
_exit (EXIT_FAILURE);
}
+ user_ns = open_namespace (pid_to_join, "user");
+ if (user_ns < 0)
+ return user_ns;
+ mnt_ns = open_namespace (pid_to_join, "mnt");
+ if (mnt_ns < 0)
+ {
+ close (user_ns);
+ return mnt_ns;
+ }
+
pid = fork ();
if (pid < 0)
fprintf (stderr, "cannot fork: %s\n", strerror (errno));
if (pid)
{
- /* We passed down these fds, close them. */
int f;
+
+ /* We passed down these fds, close them. */
+ close (user_ns);
+ close (mnt_ns);
+
for (f = 3; f < open_files_max_fd; f++)
if (open_files_set == NULL || FD_ISSET (f % FD_SETSIZE, &(open_files_set[f / FD_SETSIZE])))
close (f);
@@ -634,8 +648,10 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path)
_exit (EXIT_FAILURE);
}
- join_namespace_or_die (pid_to_join, "user");
- join_namespace_or_die (pid_to_join, "mnt");
+ join_namespace_or_die ("user", user_ns);
+ join_namespace_or_die ("mnt", mnt_ns);
+ close (user_ns);
+ close (mnt_ns);
if (syscall_setresgid (0, 0, 0) < 0)
{
diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go
index 1c1ed39df..c686d80fc 100644
--- a/pkg/rootlessport/rootlessport_linux.go
+++ b/pkg/rootlessport/rootlessport_linux.go
@@ -102,25 +102,27 @@ func parent() error {
return err
}
- sigC := make(chan os.Signal, 1)
- signal.Notify(sigC, unix.SIGPIPE)
- defer func() {
- // dummy signal to terminate the goroutine
- sigC <- unix.SIGKILL
- }()
+ exitC := make(chan os.Signal, 1)
+ defer close(exitC)
+
go func() {
+ sigC := make(chan os.Signal, 1)
+ signal.Notify(sigC, unix.SIGPIPE)
defer func() {
signal.Stop(sigC)
close(sigC)
}()
- s := <-sigC
- if s == unix.SIGPIPE {
- if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil {
- unix.Dup2(int(f.Fd()), 1) // nolint:errcheck
- unix.Dup2(int(f.Fd()), 2) // nolint:errcheck
- f.Close()
+ select {
+ case s := <-sigC:
+ if s == unix.SIGPIPE {
+ if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil {
+ unix.Dup2(int(f.Fd()), 1) // nolint:errcheck
+ unix.Dup2(int(f.Fd()), 2) // nolint:errcheck
+ f.Close()
+ }
}
+ case <-exitC:
}
}()
diff --git a/pkg/signal/signal_common.go b/pkg/signal/signal_common.go
new file mode 100644
index 000000000..8ff4b4dbf
--- /dev/null
+++ b/pkg/signal/signal_common.go
@@ -0,0 +1,41 @@
+package signal
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "syscall"
+)
+
+// ParseSignal translates a string to a valid syscall signal.
+// It returns an error if the signal map doesn't include the given signal.
+func ParseSignal(rawSignal string) (syscall.Signal, error) {
+ s, err := strconv.Atoi(rawSignal)
+ if err == nil {
+ if s == 0 {
+ return -1, fmt.Errorf("invalid signal: %s", rawSignal)
+ }
+ return syscall.Signal(s), nil
+ }
+ sig, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
+ if !ok {
+ return -1, fmt.Errorf("invalid signal: %s", rawSignal)
+ }
+ return sig, nil
+}
+
+// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input
+// can be a name or number representation i.e. "KILL" "9"
+func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
+ basename := strings.TrimPrefix(rawSignal, "-")
+ s, err := ParseSignal(basename)
+ if err == nil {
+ return s, nil
+ }
+ for k, v := range signalMap {
+ if k == strings.ToUpper(basename) {
+ return v, nil
+ }
+ }
+ return -1, fmt.Errorf("invalid signal: %s", basename)
+}
diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go
index e6e0f1ca1..6eebf7e5a 100644
--- a/pkg/signal/signal_linux.go
+++ b/pkg/signal/signal_linux.go
@@ -8,11 +8,8 @@ package signal
// NOTE: this package has originally been copied from github.com/docker/docker.
import (
- "fmt"
"os"
"os/signal"
- "strconv"
- "strings"
"syscall"
"golang.org/x/sys/unix"
@@ -94,23 +91,6 @@ var signalMap = map[string]syscall.Signal{
"RTMAX": sigrtmax,
}
-// ParseSignal translates a string to a valid syscall signal.
-// It returns an error if the signal map doesn't include the given signal.
-func ParseSignal(rawSignal string) (syscall.Signal, error) {
- s, err := strconv.Atoi(rawSignal)
- if err == nil {
- if s == 0 {
- return -1, fmt.Errorf("invalid signal: %s", rawSignal)
- }
- return syscall.Signal(s), nil
- }
- sig, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
- if !ok {
- return -1, fmt.Errorf("invalid signal: %s", rawSignal)
- }
- return sig, nil
-}
-
// CatchAll catches all signals and relays them to the specified channel.
func CatchAll(sigc chan os.Signal) {
var handledSigs []os.Signal
@@ -125,19 +105,3 @@ func StopCatch(sigc chan os.Signal) {
signal.Stop(sigc)
close(sigc)
}
-
-// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input
-// can be a name or number representation i.e. "KILL" "9"
-func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
- basename := strings.TrimPrefix(rawSignal, "-")
- s, err := ParseSignal(basename)
- if err == nil {
- return s, nil
- }
- for k, v := range signalMap {
- if k == strings.ToUpper(basename) {
- return v, nil
- }
- }
- return -1, fmt.Errorf("invalid signal: %s", basename)
-}
diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go
index f946d802d..9d1733c02 100644
--- a/pkg/signal/signal_unsupported.go
+++ b/pkg/signal/signal_unsupported.go
@@ -4,17 +4,88 @@
package signal
import (
- "fmt"
"os"
"syscall"
)
-const SIGWINCH = syscall.Signal(0xff)
+const (
+ sigrtmin = 34
+ sigrtmax = 64
-// ParseSignal translates a string to a valid syscall signal.
-// It returns an error if the signal map doesn't include the given signal.
-func ParseSignal(rawSignal string) (syscall.Signal, error) {
- return 0, fmt.Errorf("unsupported on non-linux platforms")
+ SIGWINCH = syscall.Signal(0xff)
+)
+
+// signalMap is a map of Linux signals.
+// These constants are sourced from the Linux version of golang.org/x/sys/unix
+// (I don't see much risk of this changing).
+// This should work as long as Podman only runs containers on Linux, which seems
+// a safe assumption for now.
+var signalMap = map[string]syscall.Signal{
+ "ABRT": syscall.Signal(0x6),
+ "ALRM": syscall.Signal(0xe),
+ "BUS": syscall.Signal(0x7),
+ "CHLD": syscall.Signal(0x11),
+ "CLD": syscall.Signal(0x11),
+ "CONT": syscall.Signal(0x12),
+ "FPE": syscall.Signal(0x8),
+ "HUP": syscall.Signal(0x1),
+ "ILL": syscall.Signal(0x4),
+ "INT": syscall.Signal(0x2),
+ "IO": syscall.Signal(0x1d),
+ "IOT": syscall.Signal(0x6),
+ "KILL": syscall.Signal(0x9),
+ "PIPE": syscall.Signal(0xd),
+ "POLL": syscall.Signal(0x1d),
+ "PROF": syscall.Signal(0x1b),
+ "PWR": syscall.Signal(0x1e),
+ "QUIT": syscall.Signal(0x3),
+ "SEGV": syscall.Signal(0xb),
+ "STKFLT": syscall.Signal(0x10),
+ "STOP": syscall.Signal(0x13),
+ "SYS": syscall.Signal(0x1f),
+ "TERM": syscall.Signal(0xf),
+ "TRAP": syscall.Signal(0x5),
+ "TSTP": syscall.Signal(0x14),
+ "TTIN": syscall.Signal(0x15),
+ "TTOU": syscall.Signal(0x16),
+ "URG": syscall.Signal(0x17),
+ "USR1": syscall.Signal(0xa),
+ "USR2": syscall.Signal(0xc),
+ "VTALRM": syscall.Signal(0x1a),
+ "WINCH": syscall.Signal(0x1c),
+ "XCPU": syscall.Signal(0x18),
+ "XFSZ": syscall.Signal(0x19),
+ "RTMIN": sigrtmin,
+ "RTMIN+1": sigrtmin + 1,
+ "RTMIN+2": sigrtmin + 2,
+ "RTMIN+3": sigrtmin + 3,
+ "RTMIN+4": sigrtmin + 4,
+ "RTMIN+5": sigrtmin + 5,
+ "RTMIN+6": sigrtmin + 6,
+ "RTMIN+7": sigrtmin + 7,
+ "RTMIN+8": sigrtmin + 8,
+ "RTMIN+9": sigrtmin + 9,
+ "RTMIN+10": sigrtmin + 10,
+ "RTMIN+11": sigrtmin + 11,
+ "RTMIN+12": sigrtmin + 12,
+ "RTMIN+13": sigrtmin + 13,
+ "RTMIN+14": sigrtmin + 14,
+ "RTMIN+15": sigrtmin + 15,
+ "RTMAX-14": sigrtmax - 14,
+ "RTMAX-13": sigrtmax - 13,
+ "RTMAX-12": sigrtmax - 12,
+ "RTMAX-11": sigrtmax - 11,
+ "RTMAX-10": sigrtmax - 10,
+ "RTMAX-9": sigrtmax - 9,
+ "RTMAX-8": sigrtmax - 8,
+ "RTMAX-7": sigrtmax - 7,
+ "RTMAX-6": sigrtmax - 6,
+ "RTMAX-5": sigrtmax - 5,
+ "RTMAX-4": sigrtmax - 4,
+ "RTMAX-3": sigrtmax - 3,
+ "RTMAX-2": sigrtmax - 2,
+ "RTMAX-1": sigrtmax - 1,
+ "RTMAX": sigrtmax,
}
// CatchAll catches all signals and relays them to the specified channel.
@@ -26,9 +97,3 @@ func CatchAll(sigc chan os.Signal) {
func StopCatch(sigc chan os.Signal) {
panic("Unsupported on non-linux platforms")
}
-
-// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input
-// can be a name or number representation i.e. "KILL" "9"
-func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
- return 0, fmt.Errorf("unsupported on non-linux platforms")
-}
diff --git a/pkg/spec/namespaces.go b/pkg/spec/namespaces.go
index aebc90f68..40364b054 100644
--- a/pkg/spec/namespaces.go
+++ b/pkg/spec/namespaces.go
@@ -17,6 +17,10 @@ import (
"github.com/sirupsen/logrus"
)
+// DefaultKernelNamespaces is a comma-separated list of default kernel
+// namespaces.
+const DefaultKernelNamespaces = "cgroup,ipc,net,uts"
+
// ToCreateOptions converts the input to a slice of container create options.
func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) {
var portBindings []ocicni.PortMapping
@@ -154,9 +158,9 @@ func (c *NetworkConfig) ConfigureGenerator(g *generate.Generator) error {
}
if c.PublishAll {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue
} else {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse
}
return nil
diff --git a/pkg/spec/security.go b/pkg/spec/security.go
index 0f8d36f00..6d74e97e6 100644
--- a/pkg/spec/security.go
+++ b/pkg/spec/security.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/common/pkg/capabilities"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/util"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
@@ -184,11 +185,11 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon
}
switch splitOpt[0] {
case "label":
- configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1]
+ configSpec.Annotations[define.InspectAnnotationLabel] = splitOpt[1]
case "seccomp":
- configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1]
+ configSpec.Annotations[define.InspectAnnotationSeccomp] = splitOpt[1]
case "apparmor":
- configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1]
+ configSpec.Annotations[define.InspectAnnotationApparmor] = splitOpt[1]
}
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index a62344640..25cad9578 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -7,6 +7,7 @@ import (
cconfig "github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/sysinfo"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/env"
"github.com/containers/libpod/pkg/rootless"
@@ -16,6 +17,8 @@ import (
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
)
const CpuPeriod = 100000
@@ -434,29 +437,29 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
if config.CidFile != "" {
- configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile
+ configSpec.Annotations[define.InspectAnnotationCIDFile] = config.CidFile
}
if config.Rm {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse
}
if len(config.VolumesFrom) > 0 {
- configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",")
+ configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",")
}
if config.Security.Privileged {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse
}
if config.Init {
- configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse
}
return configSpec, nil
@@ -534,11 +537,39 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error {
// If not explicitly overridden by the user, default number of open
// files and number of processes to the maximum they can be set to
// (without overriding a sysctl)
- if !nofileSet && !isRootless {
- g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax)
+ if !nofileSet {
+ max := kernelMax
+ current := kernelMax
+ if isRootless {
+ var rlimit unix.Rlimit
+ if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil {
+ logrus.Warnf("failed to return RLIMIT_NOFILE ulimit %q", err)
+ }
+ if rlimit.Cur < current {
+ current = rlimit.Cur
+ }
+ if rlimit.Max < max {
+ max = rlimit.Max
+ }
+ }
+ g.AddProcessRlimits("RLIMIT_NOFILE", max, current)
}
- if !nprocSet && !isRootless {
- g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax)
+ if !nprocSet {
+ max := kernelMax
+ current := kernelMax
+ if isRootless {
+ var rlimit unix.Rlimit
+ if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil {
+ logrus.Warnf("failed to return RLIMIT_NPROC ulimit %q", err)
+ }
+ if rlimit.Cur < current {
+ current = rlimit.Cur
+ }
+ if rlimit.Max < max {
+ max = rlimit.Max
+ }
+ }
+ g.AddProcessRlimits("RLIMIT_NPROC", max, current)
}
return nil
diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go
index 1b2a2ac32..f4cf0c704 100644
--- a/pkg/specgen/generate/config_linux.go
+++ b/pkg/specgen/generate/config_linux.go
@@ -72,13 +72,17 @@ func addPrivilegedDevices(g *generate.Generator) error {
newMounts = append(newMounts, devMnt)
}
g.Config.Mounts = append(newMounts, g.Config.Mounts...)
- g.Config.Linux.Resources.Devices = nil
+ if g.Config.Linux.Resources != nil {
+ g.Config.Linux.Resources.Devices = nil
+ }
} else {
for _, d := range hostDevices {
g.AddDevice(Device(d))
}
// Add resources device - need to clear the existing one first.
- g.Config.Linux.Resources.Devices = nil
+ if g.Config.Linux.Resources != nil {
+ g.Config.Linux.Resources.Devices = nil
+ }
g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")
}
diff --git a/pkg/specgen/generate/config_linux_nocgo.go b/pkg/specgen/generate/config_linux_nocgo.go
index fc8ed206d..81d1c7011 100644
--- a/pkg/specgen/generate/config_linux_nocgo.go
+++ b/pkg/specgen/generate/config_linux_nocgo.go
@@ -5,10 +5,11 @@ package generate
import (
"errors"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/specgen"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
-func (s *specgen.SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
+func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
return nil, errors.New("not implemented")
}
diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go
index 92a2b4d35..a217125f4 100644
--- a/pkg/specgen/generate/container.go
+++ b/pkg/specgen/generate/container.go
@@ -9,6 +9,7 @@ import (
envLib "github.com/containers/libpod/pkg/env"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
"golang.org/x/sys/unix"
)
@@ -41,31 +42,37 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
if err != nil {
return err
}
- sig, err := signal.ParseSignalNameOrNumber(stopSignal)
- if err != nil {
- return err
+ if stopSignal != "" {
+ sig, err := signal.ParseSignalNameOrNumber(stopSignal)
+ if err != nil {
+ return err
+ }
+ s.StopSignal = &sig
}
- s.StopSignal = &sig
+ }
+
+ rtc, err := r.GetConfig()
+ if err != nil {
+ return err
+ }
+ // Get Default Environment
+ defaultEnvs, err := envLib.ParseSlice(rtc.Containers.Env)
+ if err != nil {
+ return errors.Wrap(err, "Env fields in containers.conf failed to parse")
}
// Image envs from the image if they don't exist
- // already
- env, err := newImage.Env(ctx)
+ // already, overriding the default environments
+ imageEnvs, err := newImage.Env(ctx)
if err != nil {
return err
}
- if len(env) > 0 {
- envs, err := envLib.ParseSlice(env)
- if err != nil {
- return err
- }
- for k, v := range envs {
- if _, exists := s.Env[k]; !exists {
- s.Env[v] = k
- }
- }
+ envs, err := envLib.ParseSlice(imageEnvs)
+ if err != nil {
+ return errors.Wrap(err, "Env fields from image failed to parse")
}
+ s.Env = envLib.Join(envLib.Join(defaultEnvs, envs), s.Env)
labels, err := newImage.Labels(ctx)
if err != nil {
@@ -73,6 +80,9 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
}
// labels from the image that dont exist already
+ if len(labels) > 0 && s.Labels == nil {
+ s.Labels = make(map[string]string)
+ }
for k, v := range labels {
if _, exists := s.Labels[k]; !exists {
s.Labels[k] = v
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 01ddcf9c8..ffd7fd4dd 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -24,11 +24,10 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
// If joining a pod, retrieve the pod for use.
var pod *libpod.Pod
if s.Pod != "" {
- foundPod, err := rt.LookupPod(s.Pod)
+ pod, err = rt.LookupPod(s.Pod)
if err != nil {
return nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod)
}
- pod = foundPod
}
// Set defaults for unset namespaces
@@ -86,7 +85,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if err != nil {
return nil, err
}
- options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
+ imgName := s.Image
+ names := newImage.Names()
+ if len(names) > 0 {
+ imgName = names[0]
+ }
+ options = append(options, libpod.WithRootFSFromImage(newImage.ID(), imgName, s.Image))
}
if err := s.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid config provided")
@@ -97,7 +101,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, err
}
- opts, err := createContainerOptions(rt, s, pod, finalVolumes)
+ opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage)
if err != nil {
return nil, err
}
@@ -107,7 +111,8 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if err != nil {
return nil, err
}
- options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath))
+ // TODO: Enable syslog support - we'll need to put this in SpecGen.
+ options = append(options, libpod.WithExitCommand(CreateExitCommandArgs(rt.StorageConfig(), rtc, podmanPath, false, s.Remove, false)))
runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts)
if err != nil {
@@ -116,7 +121,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return rt.NewContainer(ctx, runtimeSpec, options...)
}
-func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) {
+func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@@ -130,16 +135,12 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
logrus.Debugf("setting container name %s", s.Name)
options = append(options, libpod.WithName(s.Name))
}
- if s.Pod != "" {
- pod, err := rt.LookupPod(s.Pod)
- if err != nil {
- return nil, err
- }
- logrus.Debugf("adding container to pod %s", s.Pod)
+ if pod != nil {
+ logrus.Debugf("adding container to pod %s", pod.Name())
options = append(options, rt.WithPod(pod))
}
destinations := []string{}
- // // Take all mount and named volume destinations.
+ // Take all mount and named volume destinations.
for _, mount := range s.Mounts {
destinations = append(destinations, mount.Destination)
}
@@ -160,11 +161,12 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, libpod.WithNamedVolumes(vols))
}
- if len(s.Command) != 0 {
+ if s.Command != nil {
options = append(options, libpod.WithCommand(s.Command))
}
-
- options = append(options, libpod.WithEntrypoint(s.Entrypoint))
+ if s.Entrypoint != nil {
+ options = append(options, libpod.WithEntrypoint(s.Entrypoint))
+ }
if s.StopSignal != nil {
options = append(options, libpod.WithStopSignal(*s.StopSignal))
}
@@ -192,7 +194,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
- namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod)
+ namespaceOptions, err := GenerateNamespaceOptions(ctx, s, rt, pod, img)
if err != nil {
return nil, err
}
@@ -227,7 +229,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
return options, nil
}
-func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption {
+func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Config, podmanPath string, syslog, rm bool, exec bool) []string {
// We need a cleanup process for containers in the current model.
// But we can't assume that the caller is Podman - it could be another
// user of the API.
@@ -254,14 +256,18 @@ func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.Sto
command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...)
}
- // TODO Mheon wants to leave this for now
- //if s.sys {
- // command = append(command, "--syslog", "true")
- //}
+ if syslog {
+ command = append(command, "--syslog", "true")
+ }
command = append(command, []string{"container", "cleanup"}...)
- if s.Remove {
+ if rm {
command = append(command, "--rm")
}
- return libpod.WithExitCommand(command)
+
+ if exec {
+ command = append(command, "--exec")
+ }
+
+ return command
}
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index a8b74b504..138d9e0cd 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -1,13 +1,14 @@
package generate
import (
+ "context"
"os"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util"
@@ -49,51 +50,26 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
}
}
- // If we have containers.conf and are not using cgroupns, use that.
- if cfg != nil && nsType != "cgroup" {
- switch nsType {
- case "pid":
- return specgen.ParseNamespace(cfg.Containers.PidNS)
- case "ipc":
- return specgen.ParseNamespace(cfg.Containers.IPCNS)
- case "uts":
- return specgen.ParseNamespace(cfg.Containers.UTSNS)
- case "user":
- return specgen.ParseUserNamespace(cfg.Containers.UserNS)
- case "net":
- ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS)
- return ns, err
- }
+ if cfg == nil {
+ cfg = &config.Config{}
}
-
switch nsType {
- case "pid", "ipc", "uts":
- // PID, IPC, UTS both default to private, do nothing
+ case "pid":
+ return specgen.ParseNamespace(cfg.Containers.PidNS)
+ case "ipc":
+ return specgen.ParseNamespace(cfg.Containers.IPCNS)
+ case "uts":
+ return specgen.ParseNamespace(cfg.Containers.UTSNS)
case "user":
- // User namespace always defaults to host
- toReturn.NSMode = specgen.Host
- case "net":
- // Net defaults to Slirp on rootless, Bridge otherwise.
- if rootless.IsRootless() {
- toReturn.NSMode = specgen.Slirp
- } else {
- toReturn.NSMode = specgen.Bridge
- }
+ return specgen.ParseUserNamespace(cfg.Containers.UserNS)
case "cgroup":
- // Cgroup is host for v1, private for v2.
- // We can't trust c/common for this, as it only assumes private.
- cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
- if err != nil {
- return toReturn, err
- }
- if !cgroupsv2 {
- toReturn.NSMode = specgen.Host
- }
- default:
- return toReturn, errors.Wrapf(define.ErrInvalidArg, "invalid namespace type %s passed", nsType)
+ return specgen.ParseCgroupNamespace(cfg.Containers.CgroupNS)
+ case "net":
+ ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS)
+ return ns, err
}
- return toReturn, nil
+ return toReturn, errors.Wrapf(define.ErrInvalidArg, "invalid namespace type %q passed", nsType)
}
// GenerateNamespaceOptions generates container creation options for all
@@ -102,7 +78,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
// joining a pod.
// TODO: Consider grouping options that are not directly attached to a namespace
// elsewhere.
-func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
+func GenerateNamespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, img *image.Image) ([]libpod.CtrCreateOption, error) {
toReturn := []libpod.CtrCreateOption{}
// If pod is not nil, get infra container.
@@ -230,7 +206,6 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod
}
// Net
- // TODO image ports
// TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we
// are in bridge mode.
postConfigureNetNS := !s.UserNS.IsHost()
@@ -247,9 +222,17 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod
}
toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
case specgen.Slirp:
- toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil))
+ portMappings, err := createPortMappings(ctx, s, img)
+ if err != nil {
+ return nil, err
+ }
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "slirp4netns", nil))
case specgen.Bridge:
- toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks))
+ portMappings, err := createPortMappings(ctx, s, img)
+ if err != nil {
+ return nil, err
+ }
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.CNINetworks))
}
if s.UseImageHosts {
@@ -454,10 +437,10 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt
if g.Config.Annotations == nil {
g.Config.Annotations = make(map[string]string)
}
- if s.PublishImagePorts {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ if s.PublishExposedPorts {
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue
} else {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse
}
return nil
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 87262684e..11b18e2d0 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/specgen"
@@ -13,6 +14,8 @@ import (
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
)
func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error {
@@ -41,11 +44,31 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error {
// If not explicitly overridden by the user, default number of open
// files and number of processes to the maximum they can be set to
// (without overriding a sysctl)
- if !nofileSet && !isRootless {
- g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax)
- }
- if !nprocSet && !isRootless {
- g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax)
+ if !nofileSet {
+ max := kernelMax
+ current := kernelMax
+ if isRootless {
+ var rlimit unix.Rlimit
+ if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil {
+ logrus.Warnf("failed to return RLIMIT_NOFILE ulimit %q", err)
+ }
+ current = rlimit.Cur
+ max = rlimit.Max
+ }
+ g.AddProcessRlimits("RLIMIT_NOFILE", current, max)
+ }
+ if !nprocSet {
+ max := kernelMax
+ current := kernelMax
+ if isRootless {
+ var rlimit unix.Rlimit
+ if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil {
+ logrus.Warnf("failed to return RLIMIT_NPROC ulimit %q", err)
+ }
+ current = rlimit.Cur
+ max = rlimit.Max
+ }
+ g.AddProcessRlimits("RLIMIT_NPROC", current, max)
}
return nil
@@ -67,7 +90,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image
finalCommand = append(finalCommand, entrypoint...)
command := s.Command
- if len(command) == 0 && img != nil {
+ if command == nil && img != nil {
newCmd, err := img.Cmd(ctx)
if err != nil {
return nil, err
@@ -245,6 +268,13 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
return nil, err
}
} else {
+ // add default devices from containers.conf
+ for _, device := range rtc.Containers.Devices {
+ if err := DevicesFromPath(&g, device); err != nil {
+ return nil, err
+ }
+ }
+ // add default devices specified by caller
for _, device := range s.Devices {
if err := DevicesFromPath(&g, device.Path); err != nil {
return nil, err
@@ -275,7 +305,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
}
configSpec := g.Config
- if err := securityConfigureGenerator(s, &g, newImage); err != nil {
+ if err := securityConfigureGenerator(s, &g, newImage, rtc); err != nil {
return nil, err
}
@@ -291,35 +321,27 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
configSpec.Annotations = make(map[string]string)
}
- // TODO cidfile is not in specgen; when wiring up cli, we will need to move this out of here
- // leaving as a reminder
- //if config.CidFile != "" {
- // configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile
- //}
-
if s.Remove {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse
}
if len(s.VolumesFrom) > 0 {
- configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
+ configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
}
if s.Privileged {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse
}
- // TODO Init might not make it into the specgen and therefore is not available here. We should deal
- // with this when we wire up the CLI; leaving as a reminder
- //if s.Init {
- // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue
- //} else {
- // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse
- //}
+ if s.Init {
+ configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseTrue
+ } else {
+ configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse
+ }
return configSpec, nil
}
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index babfba9bc..cd2d69cfb 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -5,6 +5,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -68,22 +69,28 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er
if p.NoManageResolvConf {
options = append(options, libpod.WithPodUseImageResolvConf())
}
+ if len(p.CNINetworks) > 0 {
+ options = append(options, libpod.WithPodNetworks(p.CNINetworks))
+ }
switch p.NetNS.NSMode {
- case specgen.Bridge:
+ case specgen.Bridge, specgen.Default, "":
logrus.Debugf("Pod using default network mode")
case specgen.Host:
logrus.Debugf("Pod will use host networking")
options = append(options, libpod.WithPodHostNetwork())
default:
- logrus.Debugf("Pod joining CNI networks: %v", p.CNINetworks)
- options = append(options, libpod.WithPodNetworks(p.CNINetworks))
+ return nil, errors.Errorf("pods presently do not support network mode %s", p.NetNS.NSMode)
}
if p.NoManageHosts {
options = append(options, libpod.WithPodUseImageHosts())
}
if len(p.PortMappings) > 0 {
- options = append(options, libpod.WithInfraContainerPorts(p.PortMappings))
+ ports, _, _, err := parsePortMapping(p.PortMappings)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, libpod.WithInfraContainerPorts(ports))
}
options = append(options, libpod.WithPodCgroups())
return options, nil
diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go
new file mode 100644
index 000000000..91c8e68d1
--- /dev/null
+++ b/pkg/specgen/generate/ports.go
@@ -0,0 +1,333 @@
+package generate
+
+import (
+ "context"
+ "net"
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ protoTCP = "tcp"
+ protoUDP = "udp"
+ protoSCTP = "sctp"
+)
+
+// Parse port maps to OCICNI port mappings.
+// Returns a set of OCICNI port mappings, and maps of utilized container and
+// host ports.
+func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) {
+ // First, we need to validate the ports passed in the specgen, and then
+ // convert them into CNI port mappings.
+ finalMappings := []ocicni.PortMapping{}
+
+ // To validate, we need two maps: one for host ports, one for container
+ // ports.
+ // Each is a map of protocol to map of IP address to map of port to
+ // port (for hostPortValidate, it's host port to container port;
+ // for containerPortValidate, container port to host port.
+ // These will ensure no collisions.
+ hostPortValidate := make(map[string]map[string]map[uint16]uint16)
+ containerPortValidate := make(map[string]map[string]map[uint16]uint16)
+
+ // Initialize the first level of maps (we can't really guess keys for
+ // the rest).
+ for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
+ hostPortValidate[proto] = make(map[string]map[uint16]uint16)
+ containerPortValidate[proto] = make(map[string]map[uint16]uint16)
+ }
+
+ // Iterate through all port mappings, generating OCICNI PortMapping
+ // structs and validating there is no overlap.
+ for _, port := range portMappings {
+ // First, check proto
+ protocols, err := checkProtocol(port.Protocol, true)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ // Validate host IP
+ hostIP := port.HostIP
+ if hostIP == "" {
+ hostIP = "0.0.0.0"
+ }
+ if ip := net.ParseIP(hostIP); ip == nil {
+ return nil, nil, nil, errors.Errorf("invalid IP address %s in port mapping", port.HostIP)
+ }
+
+ // Validate port numbers and range.
+ len := port.Range
+ if len == 0 {
+ len = 1
+ }
+ containerPort := port.ContainerPort
+ if containerPort == 0 {
+ return nil, nil, nil, errors.Errorf("container port number must be non-0")
+ }
+ hostPort := port.HostPort
+ if hostPort == 0 {
+ hostPort = containerPort
+ }
+ if uint32(len-1)+uint32(containerPort) > 65535 {
+ return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number")
+ }
+ if uint32(len-1)+uint32(hostPort) > 65536 {
+ return nil, nil, nil, errors.Errorf("host port range exceeds maximum allowable port number")
+ }
+
+ // Iterate through ports, populating maps to check for conflicts
+ // and generating CNI port mappings.
+ for _, p := range protocols {
+ hostIPMap := hostPortValidate[p]
+ ctrIPMap := containerPortValidate[p]
+
+ hostPortMap, ok := hostIPMap[hostIP]
+ if !ok {
+ hostPortMap = make(map[uint16]uint16)
+ hostIPMap[hostIP] = hostPortMap
+ }
+ ctrPortMap, ok := ctrIPMap[hostIP]
+ if !ok {
+ ctrPortMap = make(map[uint16]uint16)
+ ctrIPMap[hostIP] = ctrPortMap
+ }
+
+ // Iterate through all port numbers in the requested
+ // range.
+ var index uint16
+ for index = 0; index < len; index++ {
+ cPort := containerPort + index
+ hPort := hostPort + index
+
+ if cPort == 0 || hPort == 0 {
+ return nil, nil, nil, errors.Errorf("host and container ports cannot be 0")
+ }
+
+ testCPort := ctrPortMap[cPort]
+ if testCPort != 0 && testCPort != hPort {
+ // This is an attempt to redefine a port
+ return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p)
+ }
+ ctrPortMap[cPort] = hPort
+
+ testHPort := hostPortMap[hPort]
+ if testHPort != 0 && testHPort != cPort {
+ return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p)
+ }
+ hostPortMap[hPort] = cPort
+
+ // If we have an exact duplicate, just continue
+ if testCPort == hPort && testHPort == cPort {
+ continue
+ }
+
+ // We appear to be clear. Make an OCICNI port
+ // struct.
+ // Don't use hostIP - we want to preserve the
+ // empty string hostIP by default for compat.
+ cniPort := ocicni.PortMapping{
+ HostPort: int32(hPort),
+ ContainerPort: int32(cPort),
+ Protocol: p,
+ HostIP: port.HostIP,
+ }
+ finalMappings = append(finalMappings, cniPort)
+ }
+ }
+ }
+
+ return finalMappings, containerPortValidate, hostPortValidate, nil
+}
+
+// Make final port mappings for the container
+func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *image.Image) ([]ocicni.PortMapping, error) {
+ finalMappings, containerPortValidate, hostPortValidate, err := parsePortMapping(s.PortMappings)
+ if err != nil {
+ return nil, err
+ }
+
+ // If not publishing exposed ports, or if we are publishing and there is
+ // nothing to publish - then just return the port mappings we've made so
+ // far.
+ if !s.PublishExposedPorts || (len(s.Expose) == 0 && img == nil) {
+ return finalMappings, nil
+ }
+
+ logrus.Debugf("Adding exposed ports")
+
+ // We need to merge s.Expose into image exposed ports
+ expose := make(map[uint16]string)
+ for k, v := range s.Expose {
+ expose[k] = v
+ }
+ if img != nil {
+ inspect, err := img.InspectNoSize(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error inspecting image to get exposed ports")
+ }
+ for imgExpose := range inspect.Config.ExposedPorts {
+ // Expose format is portNumber[/protocol]
+ splitExpose := strings.SplitN(imgExpose, "/", 2)
+ num, err := strconv.Atoi(splitExpose[0])
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to convert image EXPOSE statement %q to port number", imgExpose)
+ }
+ if num > 65535 || num < 1 {
+ return nil, errors.Errorf("%d from image EXPOSE statement %q is not a valid port number", num, imgExpose)
+ }
+ // No need to validate protocol, we'll do it below.
+ if len(splitExpose) == 1 {
+ expose[uint16(num)] = "tcp"
+ } else {
+ expose[uint16(num)] = splitExpose[1]
+ }
+ }
+ }
+
+ // There's been a request to expose some ports. Let's do that.
+ // Start by figuring out what needs to be exposed.
+ // This is a map of container port number to protocols to expose.
+ toExpose := make(map[uint16][]string)
+ for port, proto := range expose {
+ // Validate protocol first
+ protocols, err := checkProtocol(proto, false)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port)
+ }
+
+ if port == 0 {
+ return nil, errors.Errorf("cannot expose 0 as it is not a valid port number")
+ }
+
+ // Check to see if the port is already present in existing
+ // mappings.
+ for _, p := range protocols {
+ ctrPortMap, ok := containerPortValidate[p]["0.0.0.0"]
+ if !ok {
+ ctrPortMap = make(map[uint16]uint16)
+ containerPortValidate[p]["0.0.0.0"] = ctrPortMap
+ }
+
+ if portNum := ctrPortMap[port]; portNum == 0 {
+ // We want to expose this port for this protocol
+ exposeProto, ok := toExpose[port]
+ if !ok {
+ exposeProto = []string{}
+ }
+ exposeProto = append(exposeProto, p)
+ toExpose[port] = exposeProto
+ }
+ }
+ }
+
+ // We now have a final list of ports that we want exposed.
+ // Let's find empty, unallocated host ports for them.
+ for port, protocols := range toExpose {
+ for _, p := range protocols {
+ // Find an open port on the host.
+ // I see a faint possibility that this will infinite
+ // loop trying to find a valid open port, so I've
+ // included a max-tries counter.
+ hostPort := 0
+ tries := 15
+ for hostPort == 0 && tries > 0 {
+ // We can't select a specific protocol, which is
+ // unfortunate for the UDP case.
+ candidate, err := getRandomPort()
+ if err != nil {
+ return nil, err
+ }
+
+ // Check if the host port is already bound
+ hostPortMap, ok := hostPortValidate[p]["0.0.0.0"]
+ if !ok {
+ hostPortMap = make(map[uint16]uint16)
+ hostPortValidate[p]["0.0.0.0"] = hostPortMap
+ }
+
+ if checkPort := hostPortMap[uint16(candidate)]; checkPort != 0 {
+ // Host port is already allocated, try again
+ tries--
+ continue
+ }
+
+ hostPortMap[uint16(candidate)] = port
+ hostPort = candidate
+ logrus.Debugf("Mapping exposed port %d/%s to host port %d", port, p, hostPort)
+
+ // Make a CNI port mapping
+ cniPort := ocicni.PortMapping{
+ HostPort: int32(candidate),
+ ContainerPort: int32(port),
+ Protocol: p,
+ HostIP: "",
+ }
+ finalMappings = append(finalMappings, cniPort)
+ }
+ if tries == 0 && hostPort == 0 {
+ // We failed to find an open port.
+ return nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port)
+ }
+ }
+ }
+
+ return finalMappings, nil
+}
+
+// Check a string to ensure it is a comma-separated set of valid protocols
+func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
+ protocols := make(map[string]struct{})
+ splitProto := strings.Split(protocol, ",")
+ // Don't error on duplicates - just deduplicate
+ for _, p := range splitProto {
+ switch p {
+ case protoTCP, "":
+ protocols[protoTCP] = struct{}{}
+ case protoUDP:
+ protocols[protoUDP] = struct{}{}
+ case protoSCTP:
+ if !allowSCTP {
+ return nil, errors.Errorf("protocol SCTP is not allowed for exposed ports")
+ }
+ protocols[protoSCTP] = struct{}{}
+ default:
+ return nil, errors.Errorf("unrecognized protocol %q in port mapping", p)
+ }
+ }
+
+ finalProto := []string{}
+ for p := range protocols {
+ finalProto = append(finalProto, p)
+ }
+
+ // This shouldn't be possible, but check anyways
+ if len(finalProto) == 0 {
+ return nil, errors.Errorf("no valid protocols specified for port mapping")
+ }
+
+ return finalProto, nil
+}
+
+// Find a random, open port on the host
+func getRandomPort() (int, error) {
+ l, err := net.Listen("tcp", ":0")
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to get free TCP port")
+ }
+ defer l.Close()
+ _, randomPort, err := net.SplitHostPort(l.Addr().String())
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to determine free port")
+ }
+ rp, err := strconv.Atoi(randomPort)
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to convert random port to int")
+ }
+ return rp, nil
+}
diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go
index e2da9e976..d2229b06f 100644
--- a/pkg/specgen/generate/security.go
+++ b/pkg/specgen/generate/security.go
@@ -4,6 +4,7 @@ import (
"strings"
"github.com/containers/common/pkg/capabilities"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/specgen"
@@ -55,76 +56,61 @@ func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s
return nil
}
-func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error {
+func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image, rtc *config.Config) error {
+ var (
+ caplist []string
+ err error
+ )
// HANDLE CAPABILITIES
// NOTE: Must happen before SECCOMP
if s.Privileged {
g.SetupPrivileged(true)
- }
-
- useNotRoot := func(user string) bool {
- if user == "" || user == "root" || user == "0" {
- return false
+ caplist = capabilities.AllCapabilities()
+ } else {
+ caplist, err = rtc.Capabilities(s.User, s.CapAdd, s.CapDrop)
+ if err != nil {
+ return err
}
- return true
- }
- configSpec := g.Config
- var err error
- var caplist []string
- bounding := configSpec.Process.Capabilities.Bounding
- if useNotRoot(s.User) {
- configSpec.Process.Capabilities.Bounding = caplist
- }
- caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop)
- if err != nil {
- return err
- }
- privCapsRequired := []string{}
- // If the container image specifies an label with a
- // capabilities.ContainerImageLabel then split the comma separated list
- // of capabilities and record them. This list indicates the only
- // capabilities, required to run the container.
- var capsRequiredRequested []string
- for key, val := range s.Labels {
- if util.StringInSlice(key, capabilities.ContainerImageLabels) {
- capsRequiredRequested = strings.Split(val, ",")
+ privCapsRequired := []string{}
+
+ // If the container image specifies an label with a
+ // capabilities.ContainerImageLabel then split the comma separated list
+ // of capabilities and record them. This list indicates the only
+ // capabilities, required to run the container.
+ var capsRequiredRequested []string
+ for key, val := range s.Labels {
+ if util.StringInSlice(key, capabilities.ContainerImageLabels) {
+ capsRequiredRequested = strings.Split(val, ",")
+ }
}
- }
- if !s.Privileged && len(capsRequiredRequested) > 0 {
+ if !s.Privileged && len(capsRequiredRequested) > 0 {
- // Pass capRequiredRequested in CapAdd field to normalize capabilities names
- capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil)
- if err != nil {
- logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ","))
- } else {
- // Verify all capRequiered are in the capList
- for _, cap := range capsRequired {
- if !util.StringInSlice(cap, caplist) {
- privCapsRequired = append(privCapsRequired, cap)
+ // Pass capRequiredRequested in CapAdd field to normalize capabilities names
+ capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil)
+ if err != nil {
+ logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ","))
+ } else {
+ // Verify all capRequiered are in the capList
+ for _, cap := range capsRequired {
+ if !util.StringInSlice(cap, caplist) {
+ privCapsRequired = append(privCapsRequired, cap)
+ }
}
}
- }
- if len(privCapsRequired) == 0 {
- caplist = capsRequired
- } else {
- logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ","))
+ if len(privCapsRequired) == 0 {
+ caplist = capsRequired
+ } else {
+ logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ","))
+ }
}
}
-
+ configSpec := g.Config
configSpec.Process.Capabilities.Bounding = caplist
configSpec.Process.Capabilities.Permitted = caplist
configSpec.Process.Capabilities.Inheritable = caplist
configSpec.Process.Capabilities.Effective = caplist
configSpec.Process.Capabilities.Ambient = caplist
- if useNotRoot(s.User) {
- caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop)
- if err != nil {
- return err
- }
- }
- configSpec.Process.Capabilities.Bounding = caplist
-
// HANDLE SECCOMP
if s.SeccompProfilePath != "unconfined" {
seccompConfig, err := getSeccompConfig(s, configSpec, newImage)
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index f0161a793..da1f8e8fc 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -3,6 +3,8 @@ package specgen
import (
"strings"
+ "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
)
@@ -38,6 +40,9 @@ const (
KeepID NamespaceMode = "keep-id"
// KeepId indicates to automatically create a user namespace
Auto NamespaceMode = "auto"
+ // DefaultKernelNamespaces is a comma-separated list of default kernel
+ // namespaces.
+ DefaultKernelNamespaces = "cgroup,ipc,net,uts"
)
// Namespace describes the namespace
@@ -163,7 +168,7 @@ func ParseNamespace(ns string) (Namespace, error) {
toReturn.NSMode = FromPod
case ns == "host":
toReturn.NSMode = Host
- case ns == "private":
+ case ns == "private", ns == "":
toReturn.NSMode = Private
case strings.HasPrefix(ns, "ns:"):
split := strings.SplitN(ns, ":", 2)
@@ -186,6 +191,31 @@ func ParseNamespace(ns string) (Namespace, error) {
return toReturn, nil
}
+// ParseCgroupNamespace parses a cgroup namespace specification in string
+// form.
+func ParseCgroupNamespace(ns string) (Namespace, error) {
+ toReturn := Namespace{}
+ // Cgroup is host for v1, private for v2.
+ // We can't trust c/common for this, as it only assumes private.
+ cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
+ if err != nil {
+ return toReturn, err
+ }
+ if cgroupsv2 {
+ switch ns {
+ case "host":
+ toReturn.NSMode = Host
+ case "private", "":
+ toReturn.NSMode = Private
+ default:
+ return toReturn, errors.Errorf("unrecognized namespace mode %s passed", ns)
+ }
+ } else {
+ toReturn.NSMode = Host
+ }
+ return toReturn, nil
+}
+
// ParseUserNamespace parses a user namespace specification in string
// form.
func ParseUserNamespace(ns string) (Namespace, error) {
@@ -205,6 +235,9 @@ func ParseUserNamespace(ns string) (Namespace, error) {
case ns == "keep-id":
toReturn.NSMode = KeepID
return toReturn, nil
+ case ns == "":
+ toReturn.NSMode = Host
+ return toReturn, nil
}
return ParseNamespace(ns)
}
@@ -215,9 +248,18 @@ func ParseUserNamespace(ns string) (Namespace, error) {
func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
toReturn := Namespace{}
var cniNetworks []string
+ // Net defaults to Slirp on rootless
switch {
+ case ns == "slirp4netns":
+ toReturn.NSMode = Slirp
case ns == "pod":
toReturn.NSMode = FromPod
+ case ns == "":
+ if rootless.IsRootless() {
+ toReturn.NSMode = Slirp
+ } else {
+ toReturn.NSMode = Bridge
+ }
case ns == "bridge":
toReturn.NSMode = Bridge
case ns == "none":
diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go
index 98d59549e..640447e71 100644
--- a/pkg/specgen/pod_validate.go
+++ b/pkg/specgen/pod_validate.go
@@ -1,7 +1,6 @@
package specgen
import (
- "github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
@@ -33,12 +32,12 @@ func (p *PodSpecGenerator) Validate() error {
}
// PodNetworkConfig
- if err := p.NetNS.validate(); err != nil {
+ if err := validateNetNS(&p.NetNS); err != nil {
return err
}
if p.NoInfra {
- if p.NetNS.NSMode == NoNetwork {
- return errors.New("NoInfra and a none network cannot be used toegther")
+ if p.NetNS.NSMode != Default && p.NetNS.NSMode != "" {
+ return errors.New("NoInfra and network modes cannot be used toegther")
}
if p.StaticIP != nil {
return exclusivePodOptions("NoInfra", "StaticIP")
@@ -85,18 +84,7 @@ func (p *PodSpecGenerator) Validate() error {
return exclusivePodOptions("NoManageHosts", "HostAdd")
}
- if err := p.NetNS.validate(); err != nil {
- return err
- }
-
// Set Defaults
- if p.NetNS.Value == "" {
- if rootless.IsRootless() {
- p.NetNS.NSMode = Slirp
- } else {
- p.NetNS.NSMode = Bridge
- }
- }
if len(p.InfraImage) < 1 {
p.InfraImage = containerConfig.Engine.InfraImage
}
diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go
index 3f830014d..11976233a 100644
--- a/pkg/specgen/podspecgen.go
+++ b/pkg/specgen/podspecgen.go
@@ -2,8 +2,6 @@ package specgen
import (
"net"
-
- "github.com/cri-o/ocicni/pkg/ocicni"
)
// PodBasicConfig contains basic configuration options for pods.
@@ -56,7 +54,7 @@ type PodNetworkConfig struct {
// 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.
+ // Setting this to anything except default conflicts with NoInfra=true.
// Defaults to Bridge as root and Slirp as rootless.
// Mandatory.
NetNS Namespace `json:"netns,omitempty"`
@@ -79,7 +77,7 @@ type PodNetworkConfig struct {
// 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"`
+ PortMappings []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
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 20c8f8800..bb01a5d14 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/storage"
- "github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -48,6 +47,7 @@ type ContainerBasicConfig struct {
// Optional.
Env map[string]string `json:"env,omitempty"`
// Terminal is whether the container will create a PTY.
+ // Optional.
Terminal bool `json:"terminal,omitempty"`
// Stdin is whether the container will keep its STDIN open.
Stdin bool `json:"stdin,omitempty"`
@@ -141,10 +141,6 @@ 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.
@@ -306,11 +302,23 @@ type ContainerNetworkConfig struct {
// PortBindings is a set of ports to map into the container.
// Only available if NetNS is set to bridge or slirp.
// Optional.
- PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"`
- // PublishImagePorts will publish ports specified in the image to random
- // ports outside.
- // Requires Image to be set.
- PublishImagePorts bool `json:"publish_image_ports,omitempty"`
+ PortMappings []PortMapping `json:"portmappings,omitempty"`
+ // PublishExposedPorts will publish ports specified in the image to
+ // random unused ports (guaranteed to be above 1024) on the host.
+ // This is based on ports set in Expose below, and any ports specified
+ // by the Image (if one is given).
+ // Only available if NetNS is set to Bridge or Slirp.
+ PublishExposedPorts bool `json:"publish_image_ports,omitempty"`
+ // Expose is a number of ports that will be forwarded to the container
+ // if PublishExposedPorts is set.
+ // Expose is a map of uint16 (port number) to a string representing
+ // protocol. Allowed protocols are "tcp", "udp", and "sctp", or some
+ // combination of the three separated by commas.
+ // If protocol is set to "" we will assume TCP.
+ // Only available if NetNS is set to Bridge or Slirp, and
+ // PublishExposedPorts is set.
+ // Optional.
+ Expose map[uint16]string `json:"expose,omitempty"`
// CNINetworks is a list of CNI networks to join the container to.
// If this list is empty, the default CNI network will be joined
// instead. If at least one entry is present, we will not join the
@@ -410,6 +418,35 @@ type NamedVolume struct {
Options []string
}
+// PortMapping is one or more ports that will be mapped into the container.
+type PortMapping struct {
+ // HostIP is the IP that we will bind to on the host.
+ // If unset, assumed to be 0.0.0.0 (all interfaces).
+ HostIP string `json:"host_ip,omitempty"`
+ // ContainerPort is the port number that will be exposed from the
+ // container.
+ // Mandatory.
+ ContainerPort uint16 `json:"container_port"`
+ // HostPort is the port number that will be forwarded from the host into
+ // the container.
+ // If omitted, will be assumed to be identical to
+ HostPort uint16 `json:"host_port,omitempty"`
+ // Range is the number of ports that will be forwarded, starting at
+ // HostPort and ContainerPort and counting up.
+ // This is 1-indexed, so 1 is assumed to be a single port (only the
+ // Hostport:Containerport mapping will be added), 2 is two ports (both
+ // Hostport:Containerport and Hostport+1:Containerport+1), etc.
+ // If unset, assumed to be 1 (a single port).
+ // Both hostport + range and containerport + range must be less than
+ // 65536.
+ Range uint16 `json:"range,omitempty"`
+ // Protocol is the protocol forward.
+ // Must be either "tcp", "udp", and "sctp", or some combination of these
+ // separated by commas.
+ // If unset, assumed to be TCP.
+ Protocol string `json:"protocol,omitempty"`
+}
+
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator {
csc := ContainerStorageConfig{}
diff --git a/pkg/trust/config.go b/pkg/trust/config.go
new file mode 100644
index 000000000..0bafc722b
--- /dev/null
+++ b/pkg/trust/config.go
@@ -0,0 +1,12 @@
+package trust
+
+// Trust Policy describes a basic trust policy configuration
+type TrustPolicy struct {
+ Name string `json:"name"`
+ RepoName string `json:"repo_name,omitempty"`
+ Keys []string `json:"keys,omitempty"`
+ SignatureStore string `json:"sigstore"`
+ Transport string `json:"transport"`
+ Type string `json:"type"`
+ GPGId string `json:"gpg_id,omitempty"`
+}
diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go
index 329a7c913..929223244 100644
--- a/pkg/util/mountOpts.go
+++ b/pkg/util/mountOpts.go
@@ -108,6 +108,7 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
if foundZ {
return nil, errors.Wrapf(ErrDupeMntOption, "only one of 'z' and 'Z' can be used")
}
+ foundZ = true
default:
return nil, errors.Wrapf(ErrBadMntOption, "unknown mount option %q", opt)
}
diff --git a/pkg/util/mountOpts_linux.go b/pkg/util/mountOpts_linux.go
index 3eac4dd25..bc7c675f3 100644
--- a/pkg/util/mountOpts_linux.go
+++ b/pkg/util/mountOpts_linux.go
@@ -7,7 +7,7 @@ import (
)
func getDefaultMountOptions(path string) (defaultMountOptions, error) {
- opts := defaultMountOptions{true, true, true}
+ opts := defaultMountOptions{false, true, true}
if path == "" {
return opts, nil
}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 8fba07c18..291353cad 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -331,7 +331,7 @@ func (i *VarlinkAPI) GetContainerStats(call iopodman.VarlinkCall, name string) e
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
- containerStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
+ containerStats, err := ctr.GetContainerStats(&define.ContainerStats{})
if err != nil {
if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyNoContainerRunning()
@@ -901,12 +901,12 @@ func (i *VarlinkAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.Exec
// HealthCheckRun executes defined container's healthcheck command and returns the container's health status.
func (i *VarlinkAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error {
hcStatus, err := i.Runtime.HealthCheck(nameOrID)
- if err != nil && hcStatus != libpod.HealthCheckFailure {
+ if err != nil && hcStatus != define.HealthCheckFailure {
return call.ReplyErrorOccurred(err.Error())
}
- status := libpod.HealthCheckUnhealthy
- if hcStatus == libpod.HealthCheckSuccess {
- status = libpod.HealthCheckHealthy
+ status := define.HealthCheckUnhealthy
+ if hcStatus == define.HealthCheckSuccess {
+ status = define.HealthCheckHealthy
}
return call.ReplyHealthCheckRun(status)
}
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 5a9360447..aeb3cdcb8 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -8,12 +8,12 @@ import (
"strconv"
"syscall"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ iopodman "github.com/containers/libpod/pkg/varlink"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
-
- "github.com/containers/libpod/libpod"
- iopodman "github.com/containers/libpod/pkg/varlink"
)
// CreatePod ...
@@ -263,7 +263,7 @@ func (i *VarlinkAPI) GetPodStats(call iopodman.VarlinkCall, name string) error {
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- prevStats := make(map[string]*libpod.ContainerStats)
+ prevStats := make(map[string]*define.ContainerStats)
podStats, err := pod.GetPodStats(prevStats)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
diff --git a/pkg/varlinkapi/remote_client.go b/pkg/varlinkapi/remote_client.go
index a16d11dec..88e410de6 100644
--- a/pkg/varlinkapi/remote_client.go
+++ b/pkg/varlinkapi/remote_client.go
@@ -3,14 +3,14 @@
package varlinkapi
import (
- "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
iopodman "github.com/containers/libpod/pkg/varlink"
)
// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod
// container stats
-func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats {
- cstats := libpod.ContainerStats{
+func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) define.ContainerStats {
+ cstats := define.ContainerStats{
ContainerID: stats.Id,
Name: stats.Name,
CPU: stats.Cpu,
diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go
index 82efe9b5d..308f02274 100644
--- a/pkg/varlinkapi/system.go
+++ b/pkg/varlinkapi/system.go
@@ -28,7 +28,7 @@ func (i *VarlinkAPI) GetVersion(call iopodman.VarlinkCall) error {
versionInfo.GitCommit,
time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
versionInfo.OsArch,
- versionInfo.RemoteAPIVersion,
+ versionInfo.APIVersion,
)
}
diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go
index 9df8ffcdc..aed6e054d 100644
--- a/pkg/varlinkapi/transfers.go
+++ b/pkg/varlinkapi/transfers.go
@@ -39,7 +39,7 @@ func (i *VarlinkAPI) SendFile(call iopodman.VarlinkCall, ftype string, length in
logrus.Debugf("successfully received %s", outputFile.Name())
// Send an ACK to the client
- call.Call.Writer.WriteString(outputFile.Name())
+ call.Call.Writer.WriteString(outputFile.Name() + ":")
call.Call.Writer.Flush()
return nil