aboutsummaryrefslogtreecommitdiff
path: root/pkg/api
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/api')
-rw-r--r--pkg/api/Makefile2
-rw-r--r--pkg/api/handlers/containers.go8
-rw-r--r--pkg/api/handlers/events.go44
-rw-r--r--pkg/api/handlers/generic/containers_stats.go18
-rw-r--r--pkg/api/handlers/libpod/containers.go174
-rw-r--r--pkg/api/handlers/libpod/images.go209
-rw-r--r--pkg/api/handlers/libpod/pods.go14
-rw-r--r--pkg/api/handlers/libpod/swagger.go8
-rw-r--r--pkg/api/handlers/libpod/types.go82
-rw-r--r--pkg/api/handlers/swagger.go8
-rw-r--r--pkg/api/handlers/types.go27
-rw-r--r--pkg/api/handlers/utils/handler.go8
-rw-r--r--pkg/api/server/register_containers.go2
-rw-r--r--pkg/api/server/register_events.go2
-rw-r--r--pkg/api/server/register_images.go122
-rw-r--r--pkg/api/server/register_pods.go2
-rw-r--r--pkg/api/server/server.go1
17 files changed, 608 insertions, 123 deletions
diff --git a/pkg/api/Makefile b/pkg/api/Makefile
index c68e50011..6b24bfd83 100644
--- a/pkg/api/Makefile
+++ b/pkg/api/Makefile
@@ -9,4 +9,4 @@ validate: ${SWAGGER_OUT}
${SWAGGER_OUT}:
# generate doesn't remove file on error
rm -f ${SWAGGER_OUT}
- swagger generate spec -o ${SWAGGER_OUT} -i tags.yaml -w ./
+ swagger generate spec -o ${SWAGGER_OUT} -i tags.yaml -w ./ -m
diff --git a/pkg/api/handlers/containers.go b/pkg/api/handlers/containers.go
index f180fbc2b..d7d040ce2 100644
--- a/pkg/api/handlers/containers.go
+++ b/pkg/api/handlers/containers.go
@@ -41,10 +41,9 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, errors.Wrapf(err, "unable to get state for Container %s", name))
return
}
- // If the Container is stopped already, send a 302
+ // If the Container is stopped already, send a 304
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
- utils.Error(w, http.StatusText(http.StatusNotModified), http.StatusNotModified,
- errors.Errorf("Container %s is already stopped ", name))
+ utils.WriteResponse(w, http.StatusNotModified, "")
return
}
@@ -134,8 +133,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
return
}
if state == define.ContainerStateRunning {
- msg := fmt.Sprintf("Container %s is already running", name)
- utils.Error(w, msg, http.StatusNotModified, errors.New(msg))
+ utils.WriteResponse(w, http.StatusNotModified, "")
return
}
if err := con.Start(r.Context(), false); err != nil {
diff --git a/pkg/api/handlers/events.go b/pkg/api/handlers/events.go
index 44bf35254..22dad9923 100644
--- a/pkg/api/handlers/events.go
+++ b/pkg/api/handlers/events.go
@@ -1,19 +1,24 @@
package handlers
import (
+ "encoding/json"
"fmt"
"net/http"
- "strings"
- "time"
+ "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
func GetEvents(w http.ResponseWriter, r *http.Request) {
+ var (
+ fromStart bool
+ eventsError error
+ )
query := struct {
- Since time.Time `schema:"since"`
- Until time.Time `schema:"until"`
+ Since string `schema:"since"`
+ Until string `schema:"until"`
Filters map[string][]string `schema:"filters"`
}{}
if err := decodeQuery(r, &query); err != nil {
@@ -27,15 +32,30 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
}
}
- libpodEvents, err := getRuntime(r).GetEvents(libpodFilters)
- if err != nil {
- utils.BadRequest(w, "filters", strings.Join(r.URL.Query()["filters"], ", "), err)
+ 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}
+ eventsError = getRuntime(r).Events(readOpts)
+ }()
+ if eventsError != nil {
+ utils.InternalServerError(w, eventsError)
return
}
-
- var apiEvents = make([]*Event, len(libpodEvents))
- for _, v := range libpodEvents {
- apiEvents = append(apiEvents, EventToApiEvent(v))
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ for event := range eventChannel {
+ e := EventToApiEvent(event)
+ //utils.WriteJSON(w, http.StatusOK, e)
+ coder := json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
+ if err := coder.Encode(e); err != nil {
+ logrus.Errorf("unable to write json: %q", err)
+ }
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
}
- utils.WriteJSON(w, http.StatusOK, apiEvents)
}
diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go
index f8804b5c0..19e2cc882 100644
--- a/pkg/api/handlers/generic/containers_stats.go
+++ b/pkg/api/handlers/generic/containers_stats.go
@@ -19,9 +19,6 @@ import (
const DefaultStatsPeriod = 5 * time.Second
func StatsContainer(w http.ResponseWriter, r *http.Request) {
- // 200 no error
- // 404 no such
- // 500 internal
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@@ -64,14 +61,15 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
var preCPUStats docker.CPUStats
if query.Stream {
preRead = time.Now()
+ systemUsage, _ := cgroups.GetSystemCPUUsage()
preCPUStats = docker.CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: stats.CPUNano,
- PercpuUsage: []uint64{uint64(stats.CPU)},
- UsageInKernelmode: 0,
- UsageInUsermode: 0,
+ PercpuUsage: stats.PerCPU,
+ UsageInKernelmode: stats.CPUSystemNano,
+ UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
},
- SystemUsage: 0,
+ SystemUsage: systemUsage,
OnlineCPUs: 0,
ThrottlingData: docker.ThrottlingData{},
}
@@ -125,6 +123,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
InstanceID: "",
}
+ systemUsage, _ := cgroups.GetSystemCPUUsage()
+
s := handlers.Stats{StatsJSON: docker.StatsJSON{
Stats: docker.Stats{
Read: time.Now(),
@@ -146,11 +146,11 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
CPUStats: docker.CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: cgroupStat.CPU.Usage.Total,
- PercpuUsage: []uint64{uint64(stats.CPU)},
+ PercpuUsage: cgroupStat.CPU.Usage.PerCPU,
UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
},
- SystemUsage: 0,
+ SystemUsage: systemUsage,
OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
ThrottlingData: docker.ThrottlingData{
Periods: 0,
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index 54acdbd36..e11e26510 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -1,16 +1,20 @@
package libpod
import (
- "fmt"
"net/http"
+ "path/filepath"
+ "sort"
"strconv"
+ "time"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
func StopContainer(w http.ResponseWriter, r *http.Request) {
@@ -46,12 +50,13 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
}
func ListContainers(w http.ResponseWriter, r *http.Request) {
var (
- filters []string
+ filterFuncs []libpod.ContainerFilter
+ pss []ListContainer
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
All bool `schema:"all"`
- Filter map[string][]string `schema:"filter"`
+ Filters map[string][]string `schema:"filters"`
Last int `schema:"last"`
Namespace bool `schema:"namespace"`
Pod bool `schema:"pod"`
@@ -66,6 +71,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
+
runtime := r.Context().Value("runtime").(*libpod.Runtime)
opts := shared.PsOptions{
All: query.All,
@@ -73,20 +79,55 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
Size: query.Size,
Sort: "",
Namespace: query.Namespace,
+ NoTrunc: true,
Pod: query.Pod,
Sync: query.Sync,
}
- if len(query.Filter) > 0 {
- for k, v := range query.Filter {
+ if len(query.Filters) > 0 {
+ for k, v := range query.Filters {
for _, val := range v {
- filters = append(filters, fmt.Sprintf("%s=%s", k, val))
+ generatedFunc, err := shared.GenerateContainerFilterFuncs(k, val, runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ filterFuncs = append(filterFuncs, generatedFunc)
}
}
}
- pss, err := shared.GetPsContainerOutput(runtime, opts, filters, 2)
+
+ if !query.All {
+ // The default is get only running containers. Do this with a filterfunc
+ runningOnly, err := shared.GenerateContainerFilterFuncs("status", define.ContainerStateRunning.String(), runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ filterFuncs = append(filterFuncs, runningOnly)
+ }
+
+ cons, err := runtime.GetContainers(filterFuncs...)
if err != nil {
utils.InternalServerError(w, err)
}
+ if query.Last > 0 {
+ // Sort the containers we got
+ sort.Sort(psSortCreateTime{cons})
+ // we should perform the lopping before we start getting
+ // the expensive information on containers
+ if query.Last < len(cons) {
+ cons = cons[len(cons)-query.Last:]
+ }
+ }
+ for _, con := range cons {
+ listCon, err := ListContainerBatch(runtime, con, opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ pss = append(pss, listCon)
+
+ }
utils.WriteResponse(w, http.StatusOK, pss)
}
@@ -194,3 +235,122 @@ func ShowMountedContainers(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, response)
}
+
+// BatchContainerOp is used in ps to reduce performance hits by "batching"
+// locks.
+func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.PsOptions) (ListContainer, error) {
+ var (
+ conConfig *libpod.ContainerConfig
+ conState define.ContainerStatus
+ err error
+ exitCode int32
+ exited bool
+ pid int
+ size *shared.ContainerSize
+ startedTime time.Time
+ exitedTime time.Time
+ cgroup, ipc, mnt, net, pidns, user, uts string
+ )
+
+ batchErr := ctr.Batch(func(c *libpod.Container) error {
+ conConfig = c.Config()
+ conState, err = c.State()
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain container state")
+ }
+
+ exitCode, exited, err = c.ExitCode()
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain container exit code")
+ }
+ startedTime, err = c.StartedTime()
+ if err != nil {
+ logrus.Errorf("error getting started time for %q: %v", c.ID(), err)
+ }
+ exitedTime, err = c.FinishedTime()
+ if err != nil {
+ logrus.Errorf("error getting exited time for %q: %v", c.ID(), err)
+ }
+
+ if !opts.Size && !opts.Namespace {
+ return nil
+ }
+
+ if opts.Namespace {
+ pid, err = c.PID()
+ if err != nil {
+ return errors.Wrapf(err, "unable to obtain container pid")
+ }
+ ctrPID := strconv.Itoa(pid)
+ cgroup, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup"))
+ ipc, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc"))
+ mnt, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt"))
+ net, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net"))
+ pidns, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid"))
+ user, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user"))
+ uts, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts"))
+ }
+ if opts.Size {
+ size = new(shared.ContainerSize)
+
+ rootFsSize, err := c.RootFsSize()
+ if err != nil {
+ logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err)
+ }
+
+ rwSize, err := c.RWSize()
+ if err != nil {
+ logrus.Errorf("error getting rw size for %q: %v", c.ID(), err)
+ }
+
+ size.RootFsSize = rootFsSize
+ size.RwSize = rwSize
+ }
+ return nil
+ })
+
+ if batchErr != nil {
+ return ListContainer{}, batchErr
+ }
+
+ ps := ListContainer{
+ Command: conConfig.Command,
+ Created: conConfig.CreatedTime.Unix(),
+ Exited: exited,
+ ExitCode: exitCode,
+ ExitedAt: exitedTime.Unix(),
+ ID: conConfig.ID,
+ Image: conConfig.RootfsImageName,
+ IsInfra: conConfig.IsInfra,
+ Labels: conConfig.Labels,
+ Mounts: ctr.UserVolumes(),
+ Names: []string{conConfig.Name},
+ Pid: pid,
+ Pod: conConfig.Pod,
+ Ports: conConfig.PortMappings,
+ Size: size,
+ StartedAt: startedTime.Unix(),
+ State: conState.String(),
+ }
+ if opts.Pod && len(conConfig.Pod) > 0 {
+ pod, err := rt.GetPod(conConfig.Pod)
+ if err != nil {
+ return ListContainer{}, err
+ }
+ ps.PodName = pod.Name()
+ }
+
+ if opts.Namespace {
+ ns := ListContainerNamespaces{
+ Cgroup: cgroup,
+ IPC: ipc,
+ MNT: mnt,
+ NET: net,
+ PIDNS: pidns,
+ User: user,
+ UTS: uts,
+ }
+ ps.Namespaces = ns
+ }
+ return ps, nil
+}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 6c926c45b..bcbe4977e 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -1,15 +1,23 @@
package libpod
import (
+ "context"
"fmt"
+ "io"
"io/ioutil"
"net/http"
"os"
"strconv"
+ "github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/docker/reference"
+ "github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/util"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
@@ -176,16 +184,205 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
}
func ImagesLoad(w http.ResponseWriter, r *http.Request) {
- //TODO ...
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.New("/libpod/images/load is not yet implemented"))
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Reference string `schema:"reference"`
+ }{
+ // Add defaults here once needed.
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ tmpfile, err := ioutil.TempFile("", "libpod-images-load.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ defer tmpfile.Close()
+
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+
+ tmpfile.Close()
+ loadedImage, err := runtime.LoadImage(context.Background(), query.Reference, tmpfile.Name(), os.Stderr, "")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, []handlers.LibpodImagesLoadReport{{ID: loadedImage}})
}
func ImagesImport(w http.ResponseWriter, r *http.Request) {
- //TODO ...
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.New("/libpod/images/import is not yet implemented"))
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Changes []string `schema:"changes"`
+ Message string `schema:"message"`
+ Reference string `schema:"reference"`
+ URL string `schema:"URL"`
+ }{
+ // Add defaults here once needed.
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ // Check if we need to load the image from a URL or from the request's body.
+ source := query.URL
+ if len(query.URL) == 0 {
+ tmpfile, err := ioutil.TempFile("", "libpod-images-import.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ defer tmpfile.Close()
+
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+
+ tmpfile.Close()
+ source = tmpfile.Name()
+ }
+
+ importedImage, err := runtime.Import(context.Background(), source, query.Reference, query.Changes, query.Message, true)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesImportReport{ID: importedImage})
}
func ImagesPull(w http.ResponseWriter, r *http.Request) {
- //TODO ...
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.New("/libpod/images/pull is not yet implemented"))
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ 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"`
+ AllTags bool `schema:"allTags"`
+ }{
+ 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
+ }
+
+ if len(query.Reference) == 0 {
+ utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
+ return
+ }
+ // Enforce the docker transport. This is just a precaution as some callers
+ // might accustomed to using the "transport:reference" notation. Using
+ // another than the "docker://" transport does not really make sense for a
+ // remote case. For loading tarballs, the load and import endpoints should
+ // be used.
+ imageRef, err := alltransports.ParseImageName(query.Reference)
+ if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Errorf("reference %q must be a docker reference", query.Reference))
+ return
+ } else if err != nil {
+ origErr := err
+ imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s://%s", docker.Transport.Name(), query.Reference))
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(origErr, "reference %q must be a docker reference", query.Reference))
+ return
+ }
+ }
+
+ // all-tags doesn't work with a tagged reference, so let's check early
+ namedRef, err := reference.Parse(query.Reference)
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "error parsing reference %q", query.Reference))
+ return
+ }
+ if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Errorf("reference %q must not have a tag for all-tags", query.Reference))
+ 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
+ }
+
+ // Setup the registry options
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerRegistryCreds: registryCreds,
+ OSChoice: query.OverrideOS,
+ ArchitectureChoice: query.OverrideArch,
+ }
+ if query.TLSVerify {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ // Prepare the images we want to pull
+ imagesToPull := []string{}
+ res := []handlers.LibpodImagesPullReport{}
+ imageName := namedRef.String()
+
+ if !query.AllTags {
+ imagesToPull = append(imagesToPull, imageName)
+ } else {
+ systemContext := image.GetSystemContext("", "", false)
+ tags, err := docker.GetRepositoryTags(context.Background(), systemContext, imageRef)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags"))
+ return
+ }
+ for _, tag := range tags {
+ imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag))
+ }
+ }
+
+ // Finally pull the images
+ for _, img := range imagesToPull {
+ newImage, err := runtime.ImageRuntime().New(
+ context.Background(),
+ img,
+ "",
+ "",
+ os.Stderr,
+ &dockerRegistryOptions,
+ image.SigningOptions{},
+ nil,
+ util.PullImageAlways)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "error pulling image %q", query.Reference))
+ return
+ }
+ res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()})
+ }
+
+ utils.WriteResponse(w, http.StatusOK, res)
}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 091368985..e9297d91b 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -91,7 +91,11 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
pod, err := runtime.NewPod(r.Context(), options...)
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ http_code := http.StatusInternalServerError
+ if errors.Cause(err) == define.ErrPodExists {
+ http_code = http.StatusConflict
+ }
+ utils.Error(w, "Something went wrong.", http_code, err)
return
}
utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()})
@@ -198,8 +202,7 @@ func PodStop(w http.ResponseWriter, r *http.Request) {
}
}
if allContainersStopped {
- alreadyStopped := errors.Errorf("pod %s is already stopped", pod.ID())
- utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyStopped)
+ utils.WriteResponse(w, http.StatusNotModified, "")
return
}
@@ -245,8 +248,7 @@ func PodStart(w http.ResponseWriter, r *http.Request) {
}
}
if allContainersRunning {
- alreadyRunning := errors.Errorf("pod %s is already running", pod.ID())
- utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyRunning)
+ utils.WriteResponse(w, http.StatusNotModified, "")
return
}
if _, err := pod.Start(r.Context()); err != nil {
@@ -409,5 +411,5 @@ func PodExists(w http.ResponseWriter, r *http.Request) {
utils.PodNotFound(w, name, err)
return
}
- utils.WriteResponse(w, http.StatusOK, "")
+ utils.WriteResponse(w, http.StatusNoContent, "")
}
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
new file mode 100644
index 000000000..aec30ef56
--- /dev/null
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -0,0 +1,8 @@
+package libpod
+
+// List Containers
+// swagger:response ListContainers
+type swagInspectPodResponse struct {
+ // in:body
+ Body []ListContainer
+}
diff --git a/pkg/api/handlers/libpod/types.go b/pkg/api/handlers/libpod/types.go
new file mode 100644
index 000000000..0949b2a72
--- /dev/null
+++ b/pkg/api/handlers/libpod/types.go
@@ -0,0 +1,82 @@
+package libpod
+
+import (
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+)
+
+// Listcontainer describes a container suitable for listing
+type ListContainer struct {
+ // Container command
+ Command []string
+ // Container creation time
+ Created int64
+ // If container has exited/stopped
+ Exited bool
+ // Time container exited
+ ExitedAt int64
+ // If container has exited, the return code from the command
+ ExitCode int32
+ // The unique identifier for the container
+ ID string `json:"Id"`
+ // Container image
+ Image string
+ // If this container is a Pod infra container
+ IsInfra bool
+ // Labels for container
+ Labels map[string]string
+ // User volume mounts
+ Mounts []string
+ // The names assigned to the container
+ Names []string
+ // Namespaces the container belongs to. Requires the
+ // namespace boolean to be true
+ Namespaces ListContainerNamespaces
+ // The process id of the container
+ Pid int
+ // If the container is part of Pod, the Pod ID. Requires the pod
+ // boolean to be set
+ Pod string
+ // If the container is part of Pod, the Pod name. Requires the pod
+ // boolean to be set
+ PodName string
+ // Port mappings
+ Ports []ocicni.PortMapping
+ // Size of the container rootfs. Requires the size boolean to be true
+ Size *shared.ContainerSize
+ // Time when container started
+ StartedAt int64
+ // State of container
+ State string
+}
+
+// ListContainer Namespaces contains the identifiers of the container's Linux namespaces
+type ListContainerNamespaces struct {
+ // Mount namespace
+ MNT string `json:"Mnt,omitempty"`
+ // Cgroup namespace
+ Cgroup string `json:"Cgroup,omitempty"`
+ // IPC namespace
+ IPC string `json:"Ipc,omitempty"`
+ // Network namespace
+ NET string `json:"Net,omitempty"`
+ // PID namespace
+ PIDNS string `json:"Pidns,omitempty"`
+ // UTS namespace
+ UTS string `json:"Uts,omitempty"`
+ // User namespace
+ User string `json:"User,omitempty"`
+}
+
+// sortContainers helps us set-up ability to sort by createTime
+type sortContainers []*libpod.Container
+
+func (a sortContainers) Len() int { return len(a) }
+func (a sortContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type psSortCreateTime struct{ sortContainers }
+
+func (a psSortCreateTime) Less(i, j int) bool {
+ return a.sortContainers[i].CreatedTime().Before(a.sortContainers[j].CreatedTime())
+}
diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go
index bc75777aa..10525bfc7 100644
--- a/pkg/api/handlers/swagger.go
+++ b/pkg/api/handlers/swagger.go
@@ -1,7 +1,6 @@
package handlers
import (
- "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/inspect"
@@ -104,13 +103,6 @@ type swagDockerTopResponse struct {
}
}
-// List containers
-// swagger:response LibpodListContainersResponse
-type swagLibpodListContainersResponse struct {
- // in:body
- Body []shared.PsContainerOutput
-}
-
// Inspect container
// swagger:response LibpodInspectContainerResponse
type swagLibpodInspectContainerResponse struct {
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 6169adb18..f5d9f9ad5 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -33,6 +33,18 @@ type ContainerConfig struct {
dockerContainer.Config
}
+type LibpodImagesLoadReport struct {
+ ID string `json:"id"`
+}
+
+type LibpodImagesImportReport struct {
+ ID string `json:"id"`
+}
+
+type LibpodImagesPullReport struct {
+ ID string `json:"id"`
+}
+
type ImageSummary struct {
docker.ImageSummary
CreatedTime time.Time `json:"CreatedTime,omitempty"`
@@ -49,21 +61,6 @@ type LibpodContainersPruneReport struct {
PruneError string `json:"error"`
}
-type LibpodImagesLoadReport struct {
- ID string `json:"id"`
- RepoTags []string `json:"repoTags"`
-}
-
-type LibpodImagesImportReport struct {
- ID string `json:"id"`
- RepoTags []string `json:"repoTags"`
-}
-
-type LibpodImagesPullReport struct {
- ID string `json:"id"`
- RepoTags []string `json:"repoTags"`
-}
-
type Info struct {
docker.Info
BuildahVersion string
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index 970f93791..44bcc794c 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -23,6 +23,14 @@ func IsLibpodRequest(r *http.Request) bool {
// 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
+ // include a message-body":
+ switch code {
+ case http.StatusNoContent, http.StatusNotModified: // 204, 304
+ w.WriteHeader(code)
+ return
+ }
+
switch v := value.(type) {
case string:
w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index abae81c38..ed30bc14a 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -610,7 +610,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
- // $ref: "#/responses/LibpodListContainersResponse"
+ // $ref: "#/responses/ListContainers"
// 400:
// $ref: "#/responses/BadParamError"
// 500:
diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go
index a32244f4d..090f66323 100644
--- a/pkg/api/server/register_events.go
+++ b/pkg/api/server/register_events.go
@@ -29,7 +29,7 @@ func (s *APIServer) RegisterEventsHandlers(r *mux.Router) error {
// description: JSON encoded map[string][]string of constraints
// responses:
// 200:
- // $ref: "#/responses/ok"
+ // description: returns a string of json data describing an event
// 500:
// "$ref": "#/responses/InternalError"
r.Handle(VersionedPath("/events"), APIHandler(s.Context, handlers.GetEvents))
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 2959e604a..f082c5fec 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -638,25 +638,22 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// summary: Load image
// description: Load an image (oci-archive or docker-archive) stream.
// parameters:
- // - in: query
- // name: change
- // description: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR. JSON encoded string"
- // type: string
- // - in: query
- // name: message
- // description: Set commit message for imported image
- // type: string
- // - in: body
- // name: request
- // description: tarball of container image
- // required: true
- // schema:
- // type: string
+ // - in: query
+ // name: reference
+ // description: "Optional Name[:TAG] for the image"
+ // type: string
+ // - in: formData
+ // name: upload
+ // description: tarball of container image
+ // type: file
+ // required: true
// produces:
// - application/json
// responses:
// 200:
- // $ref: "#/response/LibpodImagesLoadResponse"
+ // $ref: "#/responses/DocsLibpodImagesLoadResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, libpod.ImagesLoad)).Methods(http.MethodPost)
@@ -665,59 +662,80 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// tags:
// - images
// summary: Import image
- // description: Import a previosly exported tarball as an image.
+ // description: Import a previously exported tarball as an image.
// parameters:
- // - in: query
- // name: change
- // description: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR. JSON encoded string"
- // type: string
- // - in: query
- // name: message
- // description: Set commit message for imported image
- // type: string
- // - in: query
- // name: url
- // description: Specify a URL instead of a tarball
- // type: bool
- // - in: body
- // name: request
- // description: Tarball of (or URL to) container image
- // required: true
- // schema:
- // type: string
+ // - in: query
+ // name: changes
+ // description: "Apply the following possible instructions to the created image: CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR. JSON encoded string"
+ // type: array
+ // items:
+ // type: string
+ // - in: query
+ // name: message
+ // description: Set commit message for imported image
+ // type: string
+ // - in: query
+ // name: reference
+ // description: "Optional Name[:TAG] for the image"
+ // type: string
+ // - in: query
+ // name: url
+ // description: Load image from the specified URL
+ // type: string
+ // - in: formData
+ // name: upload
+ // type: file
+ // required: true
+ // description: tarball for imported image
// produces:
// - application/json
// responses:
// 200:
- // $ref: "#/response/LibpodImagesImportResponse"
+ // $ref: "#/responses/DocsLibpodImagesImportResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/import"), APIHandler(s.Context, libpod.ImagesImport)).Methods(http.MethodPost)
- // swagger:operation GET /libpod/images/pull libpod libpodImagesPull
+ // swagger:operation POST /libpod/images/pull libpod libpodImagesPull
// ---
// tags:
// - images
- // summary: Import image
- // description: Import a previosly exported image as a tarball.
+ // summary: Pull images
+ // description: Pull one or more images from a container registry.
// parameters:
- // - in: query
- // name: reference
- // description: Mandatory reference to the image (e.g., quay.io/image/name:tag)/
- // type: string
- // - in: query
- // name: credentials
- // description: username:password for the registry.
- // type: string
- // - in: query
- // name: tls-verify
- // description: Require TLS verification.
- // type: bool
- // default: true
+ // - in: query
+ // name: reference
+ // description: "Mandatory reference to the image (e.g., quay.io/image/name:tag)"
+ // type: string
+ // - in: query
+ // name: credentials
+ // description: "username:password for the registry"
+ // type: string
+ // - in: query
+ // name: overrideOS
+ // description: Pull image for the specified operating system.
+ // type: string
+ // - in: query
+ // name: overrideArch
+ // description: Pull image for the specified architecture.
+ // type: string
+ // - in: query
+ // name: tlsVerify
+ // description: Require TLS verification.
+ // type: boolean
+ // default: true
+ // - in: query
+ // name: allTags
+ // description: Pull all tagged images in the repository.
+ // type: boolean
// produces:
// - application/json
// responses:
// 200:
- // $ref: "#/response/LibpodImagesPullResponse"
+ // $ref: "#/responses/DocsLibpodImagesPullResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/pull"), APIHandler(s.Context, libpod.ImagesPull)).Methods(http.MethodPost)
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
index 5c7b51871..974568d47 100644
--- a/pkg/api/server/register_pods.go
+++ b/pkg/api/server/register_pods.go
@@ -41,6 +41,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// type: string
// 400:
// $ref: "#/responses/BadParamError"
+ // 409:
+ // description: pod already exists
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/prune"), APIHandler(s.Context, libpod.PodPrune)).Methods(http.MethodPost)
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index 69f04034d..2c709bc59 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -106,6 +106,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
server.RegisterContainersHandlers,
server.RegisterDistributionHandlers,
server.registerExecHandlers,
+ server.RegisterEventsHandlers,
server.registerHealthCheckHandlers,
server.registerImagesHandlers,
server.registerInfoHandlers,