diff options
Diffstat (limited to 'pkg')
48 files changed, 1771 insertions, 387 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index cada93829..170b2e24e 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -26,7 +26,7 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/adapter/shortcuts" - "github.com/containers/libpod/pkg/systemdgen" + "github.com/containers/libpod/pkg/systemd/generate" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -1154,7 +1154,7 @@ func generateServiceName(c *cliconfig.GenerateSystemdValues, ctr *libpod.Contain // generateSystemdgenContainerInfo is a helper to generate a // systemdgen.ContainerInfo for `GenerateSystemd`. -func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSystemdValues, nameOrID string, pod *libpod.Pod) (*systemdgen.ContainerInfo, bool, error) { +func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSystemdValues, nameOrID string, pod *libpod.Pod) (*generate.ContainerInfo, bool, error) { ctr, err := r.Runtime.LookupContainer(nameOrID) if err != nil { return nil, false, err @@ -1172,7 +1172,7 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst } name, serviceName := generateServiceName(c, ctr, pod) - info := &systemdgen.ContainerInfo{ + info := &generate.ContainerInfo{ ServiceName: serviceName, ContainerName: name, RestartPolicy: c.RestartPolicy, @@ -1187,7 +1187,7 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst // GenerateSystemd creates a unit file for a container or pod. func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) { - opts := systemdgen.Options{ + opts := generate.Options{ Files: c.Files, New: c.New, } @@ -1196,7 +1196,7 @@ func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (stri if info, found, err := r.generateSystemdgenContainerInfo(c, c.InputArgs[0], nil); found && err != nil { return "", err } else if found && err == nil { - return systemdgen.CreateContainerSystemdUnit(info, opts) + return generate.CreateContainerSystemdUnit(info, opts) } // --new does not support pods. @@ -1242,7 +1242,7 @@ func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (stri // Traverse the dependency graph and create systemdgen.ContainerInfo's for // each container. - containerInfos := []*systemdgen.ContainerInfo{podInfo} + containerInfos := []*generate.ContainerInfo{podInfo} for ctr, dependencies := range graph.DependencyMap() { // Skip the infra container as we already generated it. if ctr.ID() == infraID { @@ -1272,7 +1272,7 @@ func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (stri if i > 0 { builder.WriteByte('\n') } - out, err := systemdgen.CreateContainerSystemdUnit(info, opts) + out, err := generate.CreateContainerSystemdUnit(info, opts) if err != nil { return "", err } diff --git a/pkg/api/handlers/containers_create.go b/pkg/api/handlers/generic/containers_create.go index 48f0de94d..7e542752f 100644 --- a/pkg/api/handlers/containers_create.go +++ b/pkg/api/handlers/generic/containers_create.go @@ -1,4 +1,4 @@ -package handlers +package generic import ( "encoding/json" @@ -6,10 +6,10 @@ import ( "net/http" "strings" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" image2 "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/signal" @@ -17,14 +17,13 @@ import ( "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) func CreateContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) - input := CreateContainerConfig{} + input := handlers.CreateContainerConfig{} query := struct { Name string `schema:"name"` }{ @@ -52,34 +51,11 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) return } - cc.Name = query.Name - var pod *libpod.Pod - ctr, err := shared.CreateContainerFromCreateConfig(runtime, &cc, r.Context(), pod) - if err != nil { - if strings.Contains(err.Error(), "invalid log driver") { - // this does not quite work yet and needs a little more massaging - w.Header().Set("Content-Type", "text/plain; charset=us-ascii") - w.WriteHeader(http.StatusInternalServerError) - msg := fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type) - if _, err := fmt.Fprintln(w, msg); err != nil { - log.Errorf("%s: %q", msg, err) - } - //s.WriteResponse(w, http.StatusInternalServerError, fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type)) - return - } - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()")) - return - } - - response := ContainerCreateResponse{ - ID: ctr.ID(), - Warnings: []string{}} - - utils.WriteResponse(w, http.StatusCreated, response) + utils.CreateContainer(r.Context(), w, runtime, &cc) } -func makeCreateConfig(input CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { +func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { var ( err error init bool diff --git a/pkg/api/handlers/generic/images.go b/pkg/api/handlers/generic/images.go index c65db7575..1ced499d9 100644 --- a/pkg/api/handlers/generic/images.go +++ b/pkg/api/handlers/generic/images.go @@ -106,14 +106,14 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - author string - changes string - comment string - container string + Author string `schema:"author"` + Changes string `schema:"changes"` + Comment string `schema:"comment"` + Container string `schema:"container"` //fromSrc string # fromSrc is currently unused - pause bool - repo string - tag string + Pause bool `schema:"pause"` + Repo string `schema:"repo"` + Tag string `schema:"tag"` }{ // This is where you can override the golang default value for one of fields } @@ -145,22 +145,22 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } - if len(query.tag) > 0 { - tag = query.tag + if len(query.Tag) > 0 { + tag = query.Tag } - options.Message = query.comment - options.Author = query.author - options.Pause = query.pause - options.Changes = strings.Fields(query.changes) - ctr, err := runtime.LookupContainer(query.container) + options.Message = query.Comment + options.Author = query.Author + options.Pause = query.Pause + options.Changes = strings.Fields(query.Changes) + ctr, err := runtime.LookupContainer(query.Container) if err != nil { utils.Error(w, "Something went wrong.", http.StatusNotFound, err) return } // I know mitr hates this ... but doing for now - if len(query.repo) > 1 { - destImage = fmt.Sprintf("%s:%s", query.repo, tag) + if len(query.Repo) > 1 { + destImage = fmt.Sprintf("%s:%s", query.Repo, tag) } commitImage, err := ctr.Commit(r.Context(), destImage, options) @@ -179,8 +179,8 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - fromSrc string - changes []string + FromSrc string `schema:"fromSrc"` + Changes []string `schema:"changes"` }{ // This is where you can override the golang default value for one of fields } @@ -190,7 +190,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { return } // fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image. - source := query.fromSrc + source := query.FromSrc if source == "-" { f, err := ioutil.TempFile("", "api_load.tar") if err != nil { @@ -202,7 +202,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) } } - iid, err := runtime.Import(r.Context(), source, "", query.changes, "", false) + iid, err := runtime.Import(r.Context(), source, "", query.Changes, "", false) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) return @@ -238,8 +238,8 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - fromImage string - tag string + FromImage string `schema:"fromImage"` + Tag string `schema:"tag"` }{ // This is where you can override the golang default value for one of fields } @@ -254,9 +254,9 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { 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) + fromImage := query.FromImage + if len(query.Tag) >= 1 { + fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag) } // TODO diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go index bfe527c41..c9c9610bb 100644 --- a/pkg/api/handlers/generic/swagger.go +++ b/pkg/api/handlers/generic/swagger.go @@ -1,13 +1,15 @@ package generic -import "github.com/containers/libpod/pkg/api/handlers" +import ( + "github.com/containers/libpod/pkg/api/handlers/utils" +) // Create container // swagger:response ContainerCreateResponse type swagCtrCreateResponse struct { // in:body Body struct { - handlers.ContainerCreateResponse + utils.ContainerCreateResponse } } diff --git a/pkg/api/handlers/images.go b/pkg/api/handlers/images.go index cd3c0b93f..e4e394d68 100644 --- a/pkg/api/handlers/images.go +++ b/pkg/api/handlers/images.go @@ -155,7 +155,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { } results, err := image.SearchImages(query.Term, options) if err != nil { - utils.InternalServerError(w, err) + utils.BadRequest(w, "term", query.Term, err) } utils.WriteResponse(w, http.StatusOK, results) } diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go new file mode 100644 index 000000000..ebca41151 --- /dev/null +++ b/pkg/api/handlers/libpod/containers_create.go @@ -0,0 +1,29 @@ +package libpod + +import ( + "encoding/json" + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" +) + +// CreateContainer takes a specgenerator and makes a container. It returns +// the new container ID on success along with any warnings. +func CreateContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + var sg specgen.SpecGenerator + if err := json.NewDecoder(r.Body).Decode(&sg); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + ctr, err := sg.MakeContainer(runtime) + if err != nil { + utils.InternalServerError(w, err) + return + } + response := utils.ContainerCreateResponse{ID: ctr.ID()} + utils.WriteJSON(w, http.StatusCreated, response) +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index d043b1204..008b9b14b 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -99,12 +99,10 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http_code, err) return } - utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()}) + utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.ID()}) } func Pods(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 internal var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) podInspectData []*libpod.PodInspect diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index f5d9f9ad5..6268028f5 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -151,6 +151,7 @@ type ContainerTopOKBody struct { dockerContainer.ContainerTopOKBody } +// swagger:model PodCreateConfig type PodCreateConfig struct { Name string `json:"name"` CGroupParent string `json:"cgroup-parent"` @@ -351,18 +352,21 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI func LibpodToContainer(l *libpod.Container, infoData []define.InfoData) (*Container, error) { imageId, imageName := l.Image() - sizeRW, err := l.RWSize() - if err != nil { + + var ( + err error + sizeRootFs int64 + sizeRW int64 + state define.ContainerStatus + ) + + if state, err = l.State(); err != nil { return nil, err } - - SizeRootFs, err := l.RootFsSize() - if err != nil { + if sizeRW, err = l.RWSize(); err != nil { return nil, err } - - state, err := l.State() - if err != nil { + if sizeRootFs, err = l.RootFsSize(); err != nil { return nil, err } @@ -375,7 +379,7 @@ func LibpodToContainer(l *libpod.Container, infoData []define.InfoData) (*Contai Created: l.CreatedTime().Unix(), Ports: nil, SizeRw: sizeRW, - SizeRootFs: SizeRootFs, + SizeRootFs: sizeRootFs, Labels: l.Labels(), State: string(state), Status: "", @@ -545,11 +549,3 @@ func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) { } return ports, nil } - -// ContainerCreateResponse is the response struct for creating a container -type ContainerCreateResponse struct { - // ID of the container created - ID string `json:"id"` - // Warnings during container creation - Warnings []string `json:"Warnings"` -} diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index 74485edf2..402005581 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -1,6 +1,7 @@ package utils import ( + "context" "fmt" "net/http" "syscall" @@ -9,13 +10,22 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + createconfig "github.com/containers/libpod/pkg/spec" "github.com/gorilla/schema" "github.com/pkg/errors" ) +// ContainerCreateResponse is the response struct for creating a container +type ContainerCreateResponse struct { + // ID of the container created + ID string `json:"id"` + // Warnings during container creation + Warnings []string `json:"Warnings"` +} + func KillContainer(w http.ResponseWriter, r *http.Request) (*libpod.Container, error) { runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decorder").(*schema.Decoder) + decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Signal syscall.Signal `schema:"signal"` }{ @@ -119,3 +129,18 @@ func GenerateFilterFuncsFromMap(r *libpod.Runtime, filters map[string][]string) } return filterFuncs, nil } + +func CreateContainer(ctx context.Context, w http.ResponseWriter, runtime *libpod.Runtime, cc *createconfig.CreateConfig) { + var pod *libpod.Pod + ctr, err := shared.CreateContainerFromCreateConfig(runtime, cc, ctx, pod) + if err != nil { + Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()")) + return + } + + response := ContainerCreateResponse{ + ID: ctr.ID(), + Warnings: []string{}} + + WriteResponse(w, http.StatusCreated, response) +} diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index d0dfbf3ab..f68a71561 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -31,12 +31,16 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { if _, found := mux.Vars(r)["digests"]; found && query.Digests { UnSupportedParameter("digests") } + if len(query.Filters) > 0 { for k, v := range query.Filters { for _, val := range v { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } + return runtime.ImageRuntime().GetImagesWithFilters(filters) + } else { + return runtime.ImageRuntime().GetImages() } - return runtime.ImageRuntime().GetImagesWithFilters(filters) + } diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go index 4b93998ee..30a1680c9 100644 --- a/pkg/api/server/handler_api.go +++ b/pkg/api/server/handler_api.go @@ -2,32 +2,52 @@ package server import ( "context" + "fmt" "net/http" + "runtime" + "github.com/containers/libpod/pkg/api/handlers/utils" log "github.com/sirupsen/logrus" ) // APIHandler is a wrapper to enhance HandlerFunc's and remove redundant code -func APIHandler(ctx context.Context, h http.HandlerFunc) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Debugf("APIHandler -- Method: %s URL: %s", r.Method, r.URL.String()) - if err := r.ParseForm(); err != nil { - log.Infof("Failed Request: unable to parse form: %q", err) - } +func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // http.Server hides panics, we want to see them and fix the cause. + defer func() { + err := recover() + if err != nil { + buf := make([]byte, 1<<20) + n := runtime.Stack(buf, true) + log.Warnf("Recovering from podman handler panic: %v, %s", err, buf[:n]) + // Try to inform client things went south... won't work if handler already started writing response body + utils.InternalServerError(w, fmt.Errorf("%v", err)) + } + }() + + // Wrapper to hide some boiler plate + fn := func(w http.ResponseWriter, r *http.Request) { + // Connection counting, ugh. Needed to support the sliding window for idle checking. + s.ConnectionCh <- EnterHandler + defer func() { s.ConnectionCh <- ExitHandler }() + + log.Debugf("APIHandler -- Method: %s URL: %s (conn %d/%d)", + r.Method, r.URL.String(), s.ActiveConnections, s.TotalConnections) - // TODO: Use ConnContext when ported to go 1.13 - c := context.WithValue(r.Context(), "decoder", ctx.Value("decoder")) - c = context.WithValue(c, "runtime", ctx.Value("runtime")) - c = context.WithValue(c, "shutdownFunc", ctx.Value("shutdownFunc")) - r = r.WithContext(c) + if err := r.ParseForm(); err != nil { + log.Infof("Failed Request: unable to parse form: %q", err) + } - h(w, r) + // TODO: Use r.ConnContext when ported to go 1.13 + c := context.WithValue(r.Context(), "decoder", s.Decoder) + c = context.WithValue(c, "runtime", s.Runtime) + c = context.WithValue(c, "shutdownFunc", s.Shutdown) + r = r.WithContext(c) - shutdownFunc := r.Context().Value("shutdownFunc").(func() error) - if err := shutdownFunc(); err != nil { - log.Errorf("Failed to shutdown Server in APIHandler(): %s", err.Error()) + h(w, r) } - }) + fn(w, r) + } } // VersionedPath prepends the version parsing code diff --git a/pkg/api/server/register_auth.go b/pkg/api/server/register_auth.go index 9f312683d..8db131153 100644 --- a/pkg/api/server/register_auth.go +++ b/pkg/api/server/register_auth.go @@ -5,7 +5,7 @@ import ( "github.com/gorilla/mux" ) -func (s *APIServer) RegisterAuthHandlers(r *mux.Router) error { - r.Handle(VersionedPath("/auth"), APIHandler(s.Context, handlers.UnsupportedHandler)) +func (s *APIServer) registerAuthHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/auth"), s.APIHandler(handlers.UnsupportedHandler)) return nil } diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index b1facc27c..6007a2d00 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -9,8 +9,8 @@ import ( "github.com/gorilla/mux" ) -func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { - // swagger:operation POST /containers/create compat containerCreate +func (s *APIServer) registerContainersHandlers(r *mux.Router) error { + // swagger:operation POST /containers/create compat createContainer // --- // summary: Create a container // tags: @@ -33,7 +33,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, handlers.CreateContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/create"), s.APIHandler(generic.CreateContainer)).Methods(http.MethodPost) // swagger:operation GET /containers/json compat listContainers // --- // tags: @@ -83,7 +83,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/json"), APIHandler(s.Context, generic.ListContainers)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/containers/json"), s.APIHandler(generic.ListContainers)).Methods(http.MethodGet) // swagger:operation POST /containers/prune compat pruneContainers // --- // tags: @@ -105,7 +105,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/DocsContainerPruneReport" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, handlers.PruneContainers)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/prune"), s.APIHandler(handlers.PruneContainers)).Methods(http.MethodPost) // swagger:operation DELETE /containers/{name} compat removeContainer // --- // tags: @@ -144,7 +144,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}"), APIHandler(s.Context, generic.RemoveContainer)).Methods(http.MethodDelete) + r.HandleFunc(VersionedPath("/containers/{name}"), s.APIHandler(generic.RemoveContainer)).Methods(http.MethodDelete) // swagger:operation GET /containers/{name}/json compat getContainer // --- // tags: @@ -171,8 +171,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/json"), APIHandler(s.Context, generic.GetContainer)).Methods(http.MethodGet) - // swagger:operation post /containers/{name}/kill compat killcontainer + r.HandleFunc(VersionedPath("/containers/{name}/json"), s.APIHandler(generic.GetContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{name}/kill compat killContainer // --- // tags: // - containers (compat) @@ -201,8 +201,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/kill"), APIHandler(s.Context, generic.KillContainer)).Methods(http.MethodPost) - // swagger:operation GET /containers/{name}/logs compat LogsFromContainer + r.HandleFunc(VersionedPath("/containers/{name}/kill"), s.APIHandler(generic.KillContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{name}/logs compat logsFromContainer // --- // tags: // - containers (compat) @@ -253,7 +253,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/logs"), APIHandler(s.Context, generic.LogsFromContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/containers/{name}/logs"), s.APIHandler(generic.LogsFromContainer)).Methods(http.MethodGet) // swagger:operation POST /containers/{name}/pause compat pauseContainer // --- // tags: @@ -275,8 +275,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost) - r.HandleFunc(VersionedPath("/containers/{name}/rename"), APIHandler(s.Context, handlers.UnsupportedHandler)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name}/pause"), s.APIHandler(handlers.PauseContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name}/rename"), s.APIHandler(handlers.UnsupportedHandler)).Methods(http.MethodPost) // swagger:operation POST /containers/{name}/restart compat restartContainer // --- // tags: @@ -301,7 +301,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name}/restart"), s.APIHandler(handlers.RestartContainer)).Methods(http.MethodPost) // swagger:operation POST /containers/{name}/start compat startContainer // --- // tags: @@ -329,7 +329,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name}/start"), s.APIHandler(handlers.StartContainer)).Methods(http.MethodPost) // swagger:operation GET /containers/{name}/stats compat statsContainer // --- // tags: @@ -356,7 +356,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/stats"), APIHandler(s.Context, generic.StatsContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/containers/{name}/stats"), s.APIHandler(generic.StatsContainer)).Methods(http.MethodGet) // swagger:operation POST /containers/{name}/stop compat stopContainer // --- // tags: @@ -384,7 +384,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name}/stop"), s.APIHandler(handlers.StopContainer)).Methods(http.MethodPost) // swagger:operation GET /containers/{name}/top compat topContainer // --- // tags: @@ -409,7 +409,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/containers/{name}/top"), s.APIHandler(handlers.TopContainer)).Methods(http.MethodGet) // swagger:operation POST /containers/{name}/unpause compat unpauseContainer // --- // tags: @@ -431,7 +431,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name}/unpause"), s.APIHandler(handlers.UnpauseContainer)).Methods(http.MethodPost) // swagger:operation POST /containers/{name}/wait compat waitContainer // --- // tags: @@ -457,8 +457,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/wait"), APIHandler(s.Context, generic.WaitContainer)).Methods(http.MethodPost) - // swagger:operation POST /containers/{name}/attach compat attach + r.HandleFunc(VersionedPath("/containers/{name}/wait"), s.APIHandler(generic.WaitContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{name}/attach compat attachContainer // --- // tags: // - containers (compat) @@ -512,8 +512,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/attach"), APIHandler(s.Context, handlers.AttachContainer)).Methods(http.MethodPost) - // swagger:operation POST /containers/{name}/resize compat resize + r.HandleFunc(VersionedPath("/containers/{name}/attach"), s.APIHandler(handlers.AttachContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{name}/resize compat resizeContainer // --- // tags: // - containers (compat) @@ -544,13 +544,37 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/{name}/resize"), APIHandler(s.Context, handlers.ResizeContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name}/resize"), s.APIHandler(handlers.ResizeContainer)).Methods(http.MethodPost) /* libpod endpoints */ - r.HandleFunc(VersionedPath("/libpod/containers/create"), APIHandler(s.Context, handlers.CreateContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/create libpod libpodContainerCreate + // --- + // summary: Create a container + // tags: + // - containers + // produces: + // - application/json + // parameters: + // - in: body + // name: create + // description: attributes for creating a container + // schema: + // $ref: "#/definitions/SpecGenerator" + // responses: + // 201: + // $ref: "#/responses/ContainerCreateResponse" + // 400: + // $ref: "#/responses/BadParamError" + // 404: + // $ref: "#/responses/NoSuchContainer" + // 409: + // $ref: "#/responses/ConflictError" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/create"), s.APIHandler(libpod.CreateContainer)).Methods(http.MethodPost) // swagger:operation GET /libpod/containers/json libpod libpodListContainers // --- // tags: @@ -615,7 +639,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/json"), APIHandler(s.Context, libpod.ListContainers)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/json"), s.APIHandler(libpod.ListContainers)).Methods(http.MethodGet) // swagger:operation POST /libpod/containers/prune libpod libpodPruneContainers // --- // tags: @@ -637,8 +661,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/DocsLibpodPruneResponse" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/prune"), APIHandler(s.Context, handlers.PruneContainers)).Methods(http.MethodPost) - // swagger:operation GET /libpod/containers/showmounted libpod showMounterContainers + r.HandleFunc(VersionedPath("/libpod/containers/prune"), s.APIHandler(handlers.PruneContainers)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/showmounted libpod showMountedContainers // --- // tags: // - containers @@ -655,7 +679,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // type: string // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/showmounted"), APIHandler(s.Context, libpod.ShowMountedContainers)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/showmounted"), s.APIHandler(libpod.ShowMountedContainers)).Methods(http.MethodGet) // swagger:operation DELETE /libpod/containers/{name} libpod libpodRemoveContainer // --- // tags: @@ -689,7 +713,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}"), APIHandler(s.Context, libpod.RemoveContainer)).Methods(http.MethodDelete) + r.HandleFunc(VersionedPath("/libpod/containers/{name}"), s.APIHandler(libpod.RemoveContainer)).Methods(http.MethodDelete) // swagger:operation GET /libpod/containers/{name}/json libpod libpodGetContainer // --- // tags: @@ -715,7 +739,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/json"), APIHandler(s.Context, libpod.GetContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/json"), s.APIHandler(libpod.GetContainer)).Methods(http.MethodGet) // swagger:operation POST /libpod/containers/{name}/kill libpod libpodKillContainer // --- // tags: @@ -744,8 +768,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/kill"), APIHandler(s.Context, libpod.KillContainer)).Methods(http.MethodGet) - // swagger:operation GET /libpod/containers/{name}/mount libpod mountContainer + r.HandleFunc(VersionedPath("/libpod/containers/{name}/kill"), s.APIHandler(libpod.KillContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{name}/mount libpod mountContainer // --- // tags: // - containers @@ -770,7 +794,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/mount"), APIHandler(s.Context, libpod.MountContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/mount"), s.APIHandler(libpod.MountContainer)).Methods(http.MethodPost) // swagger:operation POST /libpod/containers/{name}/unmount libpod libpodUnmountContainer // --- // tags: @@ -792,7 +816,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/unmount"), APIHandler(s.Context, libpod.UnmountContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/unmount"), s.APIHandler(libpod.UnmountContainer)).Methods(http.MethodPost) // swagger:operation GET /libpod/containers/{name}/logs libpod libpodLogsFromContainer // --- // tags: @@ -844,7 +868,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/logs"), APIHandler(s.Context, generic.LogsFromContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/logs"), s.APIHandler(generic.LogsFromContainer)).Methods(http.MethodGet) // swagger:operation POST /libpod/containers/{name}/pause libpod libpodPauseContainer // --- // tags: @@ -866,7 +890,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // "$ref": "#/responses/NoSuchContainer" // 500: // "$ref": "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/pause"), s.APIHandler(handlers.PauseContainer)).Methods(http.MethodPost) // swagger:operation POST /libpod/containers/{name}/restart libpod libpodRestartContainer // --- // tags: @@ -891,7 +915,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/restart"), s.APIHandler(handlers.RestartContainer)).Methods(http.MethodPost) // swagger:operation POST /libpod/containers/{name}/start libpod libpodStartContainer // --- // tags: @@ -919,7 +943,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/start"), s.APIHandler(handlers.StartContainer)).Methods(http.MethodPost) // swagger:operation GET /libpod/containers/{name}/stats libpod libpodStatsContainer // --- // tags: @@ -946,7 +970,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/stats"), APIHandler(s.Context, generic.StatsContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/stats"), s.APIHandler(generic.StatsContainer)).Methods(http.MethodGet) // swagger:operation GET /libpod/containers/{name}/top libpod libpodTopContainer // --- // tags: @@ -980,7 +1004,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/top"), s.APIHandler(handlers.TopContainer)).Methods(http.MethodGet) // swagger:operation POST /libpod/containers/{name}/unpause libpod libpodUnpauseContainer // --- // tags: @@ -1001,7 +1025,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/unpause"), s.APIHandler(handlers.UnpauseContainer)).Methods(http.MethodPost) // swagger:operation POST /libpod/containers/{name}/wait libpod libpodWaitContainer // --- // tags: @@ -1022,8 +1046,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/wait"), APIHandler(s.Context, libpod.WaitContainer)).Methods(http.MethodPost) - // swagger:operation POST /libpod/containers/{name}/exists libpod containerExists + r.HandleFunc(VersionedPath("/libpod/containers/{name}/wait"), s.APIHandler(libpod.WaitContainer)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/{name}/exists libpod containerExists // --- // tags: // - containers @@ -1044,7 +1068,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/exists"), APIHandler(s.Context, libpod.ContainerExists)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/exists"), s.APIHandler(libpod.ContainerExists)).Methods(http.MethodGet) // swagger:operation POST /libpod/containers/{name}/stop libpod libpodStopContainer // --- // tags: @@ -1071,7 +1095,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/stop"), s.APIHandler(handlers.StopContainer)).Methods(http.MethodPost) // swagger:operation POST /libpod/containers/{name}/attach libpod libpodAttach // --- // tags: @@ -1126,7 +1150,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/attach"), APIHandler(s.Context, handlers.AttachContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/attach"), s.APIHandler(handlers.AttachContainer)).Methods(http.MethodPost) // swagger:operation POST /libpod/containers/{name}/resize libpod libpodResize // --- // tags: @@ -1158,6 +1182,6 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchContainer" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), APIHandler(s.Context, handlers.ResizeContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(handlers.ResizeContainer)).Methods(http.MethodPost) return nil } diff --git a/pkg/api/server/register_distribution.go b/pkg/api/server/register_distribution.go index b0ac61fb8..f03662224 100644 --- a/pkg/api/server/register_distribution.go +++ b/pkg/api/server/register_distribution.go @@ -5,7 +5,7 @@ import ( "github.com/gorilla/mux" ) -func (s *APIServer) RegisterDistributionHandlers(r *mux.Router) error { +func (s *APIServer) registerDistributionHandlers(r *mux.Router) error { r.HandleFunc(VersionedPath("/distribution/{name}/json"), handlers.UnsupportedHandler) return nil } diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go index 090f66323..bc3b62662 100644 --- a/pkg/api/server/register_events.go +++ b/pkg/api/server/register_events.go @@ -1,11 +1,13 @@ package server import ( + "net/http" + "github.com/containers/libpod/pkg/api/handlers" "github.com/gorilla/mux" ) -func (s *APIServer) RegisterEventsHandlers(r *mux.Router) error { +func (s *APIServer) registerEventsHandlers(r *mux.Router) error { // swagger:operation GET /events system getEvents // --- // tags: @@ -32,6 +34,6 @@ func (s *APIServer) RegisterEventsHandlers(r *mux.Router) error { // description: returns a string of json data describing an event // 500: // "$ref": "#/responses/InternalError" - r.Handle(VersionedPath("/events"), APIHandler(s.Context, handlers.GetEvents)) + r.Handle(VersionedPath("/events"), s.APIHandler(handlers.GetEvents)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index dbf04dc19..ad62de3f5 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -74,7 +74,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/containers/{name}/create"), APIHandler(s.Context, handlers.CreateExec)).Methods(http.MethodPost) + r.Handle(VersionedPath("/containers/{name}/create"), s.APIHandler(handlers.CreateExec)).Methods(http.MethodPost) // swagger:operation POST /exec/{id}/start compat startExec // --- // tags: @@ -110,7 +110,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is stopped or paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/exec/{id}/start"), APIHandler(s.Context, handlers.StartExec)).Methods(http.MethodPost) + r.Handle(VersionedPath("/exec/{id}/start"), s.APIHandler(handlers.StartExec)).Methods(http.MethodPost) // swagger:operation POST /exec/{id}/resize compat resizeExec // --- // tags: @@ -141,8 +141,8 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/exec/{id}/resize"), APIHandler(s.Context, handlers.ResizeExec)).Methods(http.MethodPost) - // swagger:operation GET /exec/{id}/inspect compat inspectExec + r.Handle(VersionedPath("/exec/{id}/resize"), s.APIHandler(handlers.ResizeExec)).Methods(http.MethodPost) + // swagger:operation GET /exec/{id}/json compat inspectExec // --- // tags: // - exec (compat) @@ -163,7 +163,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/exec/{id}/json"), APIHandler(s.Context, handlers.InspectExec)).Methods(http.MethodGet) + r.Handle(VersionedPath("/exec/{id}/json"), s.APIHandler(handlers.InspectExec)).Methods(http.MethodGet) /* libpod api follows @@ -235,7 +235,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/containers/{name}/create"), APIHandler(s.Context, handlers.CreateExec)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/containers/{name}/create"), s.APIHandler(handlers.CreateExec)).Methods(http.MethodPost) // swagger:operation POST /libpod/exec/{id}/start libpod libpodStartExec // --- // tags: @@ -271,7 +271,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is stopped or paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/exec/{id}/start"), APIHandler(s.Context, handlers.StartExec)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/exec/{id}/start"), s.APIHandler(handlers.StartExec)).Methods(http.MethodPost) // swagger:operation POST /libpod/exec/{id}/resize libpod libpodResizeExec // --- // tags: @@ -302,8 +302,8 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/exec/{id}/resize"), APIHandler(s.Context, handlers.ResizeExec)).Methods(http.MethodPost) - // swagger:operation GET /libpod/exec/{id}/inspect libpod libpodInspectExec + r.Handle(VersionedPath("/libpod/exec/{id}/resize"), s.APIHandler(handlers.ResizeExec)).Methods(http.MethodPost) + // swagger:operation GET /libpod/exec/{id}/json libpod libpodInspectExec // --- // tags: // - exec @@ -324,6 +324,6 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/exec/{id}/json"), APIHandler(s.Context, handlers.InspectExec)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(handlers.InspectExec)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_healthcheck.go b/pkg/api/server/register_healthcheck.go index 1286324f0..5466e2905 100644 --- a/pkg/api/server/register_healthcheck.go +++ b/pkg/api/server/register_healthcheck.go @@ -8,6 +8,6 @@ import ( ) func (s *APIServer) registerHealthCheckHandlers(r *mux.Router) error { - r.Handle(VersionedPath("/libpod/containers/{name}/runhealthcheck"), APIHandler(s.Context, libpod.RunHealthCheck)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/containers/{name}/runhealthcheck"), s.APIHandler(libpod.RunHealthCheck)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 4706a7c69..4c8f05385 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -47,8 +47,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchImage" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromImage)).Methods(http.MethodPost).Queries("fromImage", "{fromImage}") - r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromSrc)).Methods(http.MethodPost).Queries("fromSrc", "{fromSrc}") + r.Handle(VersionedPath("/images/create"), s.APIHandler(generic.CreateImageFromImage)).Methods(http.MethodPost).Queries("fromImage", "{fromImage}") + r.Handle(VersionedPath("/images/create"), s.APIHandler(generic.CreateImageFromSrc)).Methods(http.MethodPost).Queries("fromSrc", "{fromSrc}") // swagger:operation GET /images/json compat listImages // --- // tags: @@ -83,7 +83,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/DockerImageSummary" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet) + r.Handle(VersionedPath("/images/json"), s.APIHandler(generic.GetImages)).Methods(http.MethodGet) // swagger:operation POST /images/load compat importImage // --- // tags: @@ -107,7 +107,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: no error // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, generic.LoadImages)).Methods(http.MethodPost) + r.Handle(VersionedPath("/images/load"), s.APIHandler(generic.LoadImages)).Methods(http.MethodPost) // swagger:operation POST /images/prune compat pruneImages // --- // tags: @@ -132,7 +132,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/DocsImageDeleteResponse" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/images/prune"), APIHandler(s.Context, generic.PruneImages)).Methods(http.MethodPost) + r.Handle(VersionedPath("/images/prune"), s.APIHandler(generic.PruneImages)).Methods(http.MethodPost) // swagger:operation GET /images/search compat searchImages // --- // tags: @@ -161,10 +161,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // responses: // 200: // $ref: "#/responses/DocsSearchResponse" + // 400: + // $ref: "#/responses/BadParamError" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) - // swagger:operation DELETE /images/{name} compat removeImage + r.Handle(VersionedPath("/images/search"), s.APIHandler(handlers.SearchImages)).Methods(http.MethodGet) + // swagger:operation DELETE /images/{name:.*} compat removeImage // --- // tags: // - images (compat) @@ -172,7 +174,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Delete an image from local storage // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: name or ID of image to delete @@ -195,8 +197,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/ConflictError' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/images/{name}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete) - // swagger:operation GET /images/{name}/get compat exportImage + r.Handle(VersionedPath("/images/{name:.*}"), s.APIHandler(handlers.RemoveImage)).Methods(http.MethodDelete) + // swagger:operation GET /images/{name:.*}/get compat exportImage // --- // tags: // - images (compat) @@ -204,7 +206,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Export an image in tarball format // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -218,8 +220,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // format: binary // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/images/{name}/get"), APIHandler(s.Context, generic.ExportImage)).Methods(http.MethodGet) - // swagger:operation GET /images/{name}/history compat imageHistory + r.Handle(VersionedPath("/images/{name:.*}/get"), s.APIHandler(generic.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /images/{name:.*}/history compat imageHistory // --- // tags: // - images (compat) @@ -227,7 +229,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Return parent layers of an image. // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -240,8 +242,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchImage" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/images/{name}/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet) - // swagger:operation GET /images/{name}/json compat inspectImage + r.Handle(VersionedPath("/images/{name:.*}/history"), s.APIHandler(handlers.HistoryImage)).Methods(http.MethodGet) + // swagger:operation GET /images/{name:.*}/json compat inspectImage // --- // tags: // - images (compat) @@ -249,7 +251,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Return low-level information about an image. // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -262,8 +264,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchImage" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/images/{name}/json"), APIHandler(s.Context, generic.GetImage)) - // swagger:operation POST /images/{name}/tag compat tagImage + r.Handle(VersionedPath("/images/{name:.*}/json"), s.APIHandler(generic.GetImage)).Methods(http.MethodGet) + // swagger:operation POST /images/{name:.*}/tag compat tagImage // --- // tags: // - images (compat) @@ -271,7 +273,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Tag an image so that it becomes part of a repository. // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -296,8 +298,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/ConflictError' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/images/{name}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost) - // swagger:operation POST /commit/ compat commitContainer + r.Handle(VersionedPath("/images/{name:.*}/tag"), s.APIHandler(handlers.TagImage)).Methods(http.MethodPost) + // swagger:operation POST /commit compat commitContainer // --- // tags: // - containers (compat) @@ -341,9 +343,9 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/NoSuchImage' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/commit"), APIHandler(s.Context, generic.CommitContainer)).Methods(http.MethodPost) + r.Handle(VersionedPath("/commit"), s.APIHandler(generic.CommitContainer)).Methods(http.MethodPost) - // swagger:operation POST /build images buildImage + // swagger:operation POST /build compat buildImage // --- // tags: // - images @@ -551,12 +553,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/build"), APIHandler(s.Context, handlers.BuildImage)).Methods(http.MethodPost) + r.Handle(VersionedPath("/build"), s.APIHandler(handlers.BuildImage)).Methods(http.MethodPost) /* libpod endpoints */ - // swagger:operation POST /libpod/images/{name}/exists libpod libpodImageExists + // swagger:operation GET /libpod/images/{name:.*}/exists libpod libpodImageExists // --- // tags: // - images @@ -564,7 +566,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Check if image exists in local store // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -577,8 +579,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/NoSuchImage' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/{name}/exists"), APIHandler(s.Context, libpod.ImageExists)) - // swagger:operation POST /libpod/images/{name}/tree libpod libpodImageTree + r.Handle(VersionedPath("/libpod/images/{name:.*}/exists"), s.APIHandler(libpod.ImageExists)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/{name:.*}/tree libpod libpodImageTree // --- // tags: // - images @@ -586,7 +588,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Retrieve the image tree for the provided image name or ID // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -603,8 +605,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/NoSuchImage' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/{name}/tree"), APIHandler(s.Context, libpod.ImageTree)) - // swagger:operation GET /libpod/images/{name}/history libpod libpodImageHistory + r.Handle(VersionedPath("/libpod/images/{name:.*}/tree"), s.APIHandler(libpod.ImageTree)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/{name:.*}/history libpod libpodImageHistory // --- // tags: // - images @@ -612,7 +614,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Return parent layers of an image. // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -625,7 +627,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/NoSuchImage' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/{name}/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/images/{name:.*}/history"), s.APIHandler(handlers.HistoryImage)).Methods(http.MethodGet) // swagger:operation GET /libpod/images/json libpod libpodListImages // --- // tags: @@ -655,7 +657,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/DockerImageSummary" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/json"), APIHandler(s.Context, libpod.GetImages)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/images/json"), s.APIHandler(libpod.GetImages)).Methods(http.MethodGet) // swagger:operation POST /libpod/images/load libpod libpodImagesLoad // --- // tags: @@ -681,7 +683,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, libpod.ImagesLoad)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/images/load"), s.APIHandler(libpod.ImagesLoad)).Methods(http.MethodPost) // swagger:operation POST /libpod/images/import libpod libpodImagesImport // --- // tags: @@ -721,7 +723,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/import"), APIHandler(s.Context, libpod.ImagesImport)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost) // swagger:operation POST /libpod/images/pull libpod libpodImagesPull // --- // tags: @@ -763,7 +765,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/pull"), APIHandler(s.Context, libpod.ImagesPull)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/images/pull"), s.APIHandler(libpod.ImagesPull)).Methods(http.MethodPost) // swagger:operation POST /libpod/images/prune libpod libpodPruneImages // --- // tags: @@ -788,7 +790,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/DocsImageDeleteResponse" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/prune"), APIHandler(s.Context, libpod.PruneImages)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/images/prune"), s.APIHandler(libpod.PruneImages)).Methods(http.MethodPost) // swagger:operation GET /libpod/images/search libpod libpodSearchImages // --- // tags: @@ -819,8 +821,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/DocsSearchResponse" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) - // swagger:operation DELETE /libpod/images/{name} libpod libpodRemoveImage + r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(handlers.SearchImages)).Methods(http.MethodGet) + // swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage // --- // tags: // - images @@ -828,7 +830,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Delete an image from local store // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: name or ID of image to delete @@ -849,8 +851,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/ConflictError' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/{name}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete) - // swagger:operation GET /libpod/images/{name}/get libpod libpoodExportImage + r.Handle(VersionedPath("/libpod/images/{name:.*}"), s.APIHandler(handlers.RemoveImage)).Methods(http.MethodDelete) + // swagger:operation GET /libpod/images/{name:.*}/get libpod libpodExportImage // --- // tags: // - images @@ -858,7 +860,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Export an image as a tarball // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -882,8 +884,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/NoSuchImage' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/{name}/get"), APIHandler(s.Context, libpod.ExportImage)).Methods(http.MethodGet) - // swagger:operation GET /libpod/images/{name}/json libpod libpodInspectImage + r.Handle(VersionedPath("/libpod/images/{name:.*}/get"), s.APIHandler(libpod.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/{name:.*}/json libpod libpodInspectImage // --- // tags: // - images @@ -891,7 +893,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Obtain low-level information about an image // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -904,8 +906,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/NoSuchImage' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/{name}/json"), APIHandler(s.Context, libpod.GetImage)) - // swagger:operation POST /libpod/images/{name}/tag libpod libpodTagImage + r.Handle(VersionedPath("/libpod/images/{name:.*}/json"), s.APIHandler(libpod.GetImage)).Methods(http.MethodGet) + // swagger:operation POST /libpod/images/{name:.*}/tag libpod libpodTagImage // --- // tags: // - images @@ -913,7 +915,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Tag an image so that it becomes part of a repository. // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the container @@ -938,7 +940,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: '#/responses/ConflictError' // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/{name}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/images/{name:.*}/tag"), s.APIHandler(handlers.TagImage)).Methods(http.MethodPost) return nil } diff --git a/pkg/api/server/register_info.go b/pkg/api/server/register_info.go index 8c50fed7f..36c467cc3 100644 --- a/pkg/api/server/register_info.go +++ b/pkg/api/server/register_info.go @@ -21,6 +21,6 @@ func (s *APIServer) registerInfoHandlers(r *mux.Router) error { // description: to be determined // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/info"), APIHandler(s.Context, generic.GetInfo)).Methods(http.MethodGet) + r.Handle(VersionedPath("/info"), s.APIHandler(generic.GetInfo)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_monitor.go b/pkg/api/server/register_monitor.go index e6c235419..dbe0d27ce 100644 --- a/pkg/api/server/register_monitor.go +++ b/pkg/api/server/register_monitor.go @@ -5,7 +5,7 @@ import ( "github.com/gorilla/mux" ) -func (s *APIServer) RegisterMonitorHandlers(r *mux.Router) error { - r.Handle(VersionedPath("/monitor"), APIHandler(s.Context, handlers.UnsupportedHandler)) +func (s *APIServer) registerMonitorHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/monitor"), s.APIHandler(handlers.UnsupportedHandler)) return nil } diff --git a/pkg/api/server/register_ping.go b/pkg/api/server/register_ping.go index 086e674a1..349a8a71a 100644 --- a/pkg/api/server/register_ping.go +++ b/pkg/api/server/register_ping.go @@ -9,8 +9,8 @@ import ( func (s *APIServer) registerPingHandlers(r *mux.Router) error { - r.Handle("/_ping", APIHandler(s.Context, handlers.Ping)).Methods(http.MethodGet) - r.Handle("/_ping", APIHandler(s.Context, handlers.Ping)).Methods(http.MethodHead) + r.Handle("/_ping", s.APIHandler(handlers.Ping)).Methods(http.MethodGet) + r.Handle("/_ping", s.APIHandler(handlers.Ping)).Methods(http.MethodHead) // swagger:operation GET /libpod/_ping libpod libpodPingGet // --- @@ -61,7 +61,7 @@ func (s *APIServer) registerPingHandlers(r *mux.Router) error { // determine if talking to Podman engine or another engine // 500: // $ref: "#/responses/InternalError" - r.Handle("/libpod/_ping", APIHandler(s.Context, handlers.Ping)).Methods(http.MethodGet) - r.Handle("/libpod/_ping", APIHandler(s.Context, handlers.Ping)).Methods(http.MethodHead) + r.Handle("/libpod/_ping", s.APIHandler(handlers.Ping)).Methods(http.MethodGet) + r.Handle("/libpod/_ping", s.APIHandler(handlers.Ping)).Methods(http.MethodHead) return nil } diff --git a/pkg/api/server/register_plugins.go b/pkg/api/server/register_plugins.go index 7fd6b9c4c..479a79d1f 100644 --- a/pkg/api/server/register_plugins.go +++ b/pkg/api/server/register_plugins.go @@ -5,7 +5,7 @@ import ( "github.com/gorilla/mux" ) -func (s *APIServer) RegisterPluginsHandlers(r *mux.Router) error { - r.Handle(VersionedPath("/plugins"), APIHandler(s.Context, handlers.UnsupportedHandler)) +func (s *APIServer) registerPluginsHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/plugins"), s.APIHandler(handlers.UnsupportedHandler)) return nil } diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index 974568d47..af2330665 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -25,8 +25,27 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/json"), APIHandler(s.Context, libpod.Pods)).Methods(http.MethodGet) - r.Handle(VersionedPath("/libpod/pods/create"), APIHandler(s.Context, libpod.PodCreate)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/json"), s.APIHandler(libpod.Pods)).Methods(http.MethodGet) + // swagger:operation POST /libpod/pods/create pods CreatePod + // --- + // summary: Create a pod + // produces: + // - application/json + // parameters: + // - in: body + // name: create + // description: attributes for creating a pod + // schema: + // type: object + // $ref: "#/definitions/PodCreateConfig" + // responses: + // 200: + // $ref: "#/definitions/IdResponse" + // 400: + // $ref: "#/responses/BadParamError" + // 500: + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/create"), s.APIHandler(libpod.PodCreate)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/prune pods PrunePods // --- // summary: Prune unused pods @@ -45,7 +64,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // description: pod already exists // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/prune"), APIHandler(s.Context, libpod.PodPrune)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/prune"), s.APIHandler(libpod.PodPrune)).Methods(http.MethodPost) // swagger:operation DELETE /libpod/pods/{name} pods removePod // --- // summary: Remove pod @@ -70,7 +89,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}"), APIHandler(s.Context, libpod.PodDelete)).Methods(http.MethodDelete) + r.Handle(VersionedPath("/libpod/pods/{name}"), s.APIHandler(libpod.PodDelete)).Methods(http.MethodDelete) // swagger:operation GET /libpod/pods/{name}/json pods inspectPod // --- // summary: Inspect pod @@ -89,7 +108,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/json"), APIHandler(s.Context, libpod.PodInspect)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/pods/{name}/json"), s.APIHandler(libpod.PodInspect)).Methods(http.MethodGet) // swagger:operation GET /libpod/pods/{name}/exists pods podExists // --- // summary: Pod exists @@ -109,7 +128,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/exists"), APIHandler(s.Context, libpod.PodExists)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/pods/{name}/exists"), s.APIHandler(libpod.PodExists)).Methods(http.MethodGet) // swagger:operation POST /libpod/pods/{name}/kill pods killPod // --- // summary: Kill a pod @@ -137,7 +156,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/kill"), APIHandler(s.Context, libpod.PodKill)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name}/kill"), s.APIHandler(libpod.PodKill)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{name}/pause pods pausePod // --- // summary: Pause a pod @@ -157,7 +176,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/pause"), APIHandler(s.Context, libpod.PodPause)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name}/pause"), s.APIHandler(libpod.PodPause)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{name}/restart pods restartPod // --- // summary: Restart a pod @@ -176,7 +195,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/restart"), APIHandler(s.Context, libpod.PodRestart)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name}/restart"), s.APIHandler(libpod.PodRestart)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{name}/start pods startPod // --- // summary: Start a pod @@ -197,7 +216,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/start"), APIHandler(s.Context, libpod.PodStart)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name}/start"), s.APIHandler(libpod.PodStart)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{name}/stop pods stopPod // --- // summary: Stop a pod @@ -224,7 +243,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/stop"), APIHandler(s.Context, libpod.PodStop)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name}/stop"), s.APIHandler(libpod.PodStop)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{name}/unpause pods unpausePod // --- // summary: Unpause a pod @@ -243,6 +262,6 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/pods/{name}/unpause"), APIHandler(s.Context, libpod.PodUnpause)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name}/unpause"), s.APIHandler(libpod.PodUnpause)).Methods(http.MethodPost) return nil } diff --git a/pkg/api/server/register_swarm.go b/pkg/api/server/register_swarm.go index 63d8acfde..e37ac4e41 100644 --- a/pkg/api/server/register_swarm.go +++ b/pkg/api/server/register_swarm.go @@ -9,7 +9,7 @@ import ( "github.com/sirupsen/logrus" ) -func (s *APIServer) RegisterSwarmHandlers(r *mux.Router) error { +func (s *APIServer) registerSwarmHandlers(r *mux.Router) error { r.PathPrefix("/v{version:[0-9.]+}/configs/").HandlerFunc(noSwarm) r.PathPrefix("/v{version:[0-9.]+}/nodes/").HandlerFunc(noSwarm) r.PathPrefix("/v{version:[0-9.]+}/secrets/").HandlerFunc(noSwarm) diff --git a/pkg/api/server/register_system.go b/pkg/api/server/register_system.go index f0eaeffd2..188c1cdac 100644 --- a/pkg/api/server/register_system.go +++ b/pkg/api/server/register_system.go @@ -1,11 +1,13 @@ package server import ( + "net/http" + "github.com/containers/libpod/pkg/api/handlers/generic" "github.com/gorilla/mux" ) func (s *APIServer) registerSystemHandlers(r *mux.Router) error { - r.Handle(VersionedPath("/system/df"), APIHandler(s.Context, generic.GetDiskUsage)) + r.Handle(VersionedPath("/system/df"), s.APIHandler(generic.GetDiskUsage)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_version.go b/pkg/api/server/register_version.go index d3b47c2a9..ee01ad4b3 100644 --- a/pkg/api/server/register_version.go +++ b/pkg/api/server/register_version.go @@ -1,12 +1,14 @@ package server import ( + "net/http" + "github.com/containers/libpod/pkg/api/handlers" "github.com/gorilla/mux" ) func (s *APIServer) registerVersionHandlers(r *mux.Router) error { - r.Handle("/version", APIHandler(s.Context, handlers.VersionHandler)) - r.Handle(VersionedPath("/version"), APIHandler(s.Context, handlers.VersionHandler)) + r.Handle("/version", s.APIHandler(handlers.VersionHandler)).Methods(http.MethodGet) + r.Handle(VersionedPath("/version"), s.APIHandler(handlers.VersionHandler)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go index d34c71238..efe56a3ad 100644 --- a/pkg/api/server/register_volumes.go +++ b/pkg/api/server/register_volumes.go @@ -18,9 +18,9 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // description: tbd // '500': // "$ref": "#/responses/InternalError" - r.Handle("/libpod/volumes/create", APIHandler(s.Context, libpod.CreateVolume)).Methods(http.MethodPost) - r.Handle("/libpod/volumes/json", APIHandler(s.Context, libpod.ListVolumes)).Methods(http.MethodGet) - // swagger:operation POST /volumes/prune volumes pruneVolumes + r.Handle("/libpod/volumes/create", s.APIHandler(libpod.CreateVolume)).Methods(http.MethodPost) + r.Handle("/libpod/volumes/json", s.APIHandler(libpod.ListVolumes)).Methods(http.MethodGet) + // swagger:operation POST /libpod/volumes/prune volumes pruneVolumes // --- // summary: Prune volumes // produces: @@ -30,8 +30,8 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // description: no error // '500': // "$ref": "#/responses/InternalError" - r.Handle("/libpod/volumes/prune", APIHandler(s.Context, libpod.PruneVolumes)).Methods(http.MethodPost) - // swagger:operation GET /volumes/{name}/json volumes inspectVolume + r.Handle("/libpod/volumes/prune", s.APIHandler(libpod.PruneVolumes)).Methods(http.MethodPost) + // swagger:operation GET /libpod/volumes/{name}/json volumes inspectVolume // --- // summary: Inspect volume // parameters: @@ -49,8 +49,8 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // "$ref": "#/responses/NoSuchVolume" // '500': // "$ref": "#/responses/InternalError" - r.Handle("/libpod/volumes/{name}/json", APIHandler(s.Context, libpod.InspectVolume)).Methods(http.MethodGet) - // swagger:operation DELETE /volumes/{name} volumes removeVolume + r.Handle("/libpod/volumes/{name}/json", s.APIHandler(libpod.InspectVolume)).Methods(http.MethodGet) + // swagger:operation DELETE /libpod/volumes/{name} volumes removeVolume // --- // summary: Remove volume // parameters: @@ -74,6 +74,6 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchVolume" // 500: // $ref: "#/responses/InternalError" - r.Handle("/libpod/volumes/{name}", APIHandler(s.Context, libpod.RemoveVolume)).Methods(http.MethodDelete) + r.Handle("/libpod/volumes/{name}", s.APIHandler(libpod.RemoveVolume)).Methods(http.MethodDelete) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 2c709bc59..e7b2a5525 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -20,20 +20,26 @@ import ( ) type APIServer struct { - http.Server // The HTTP work happens here - *schema.Decoder // Decoder for Query parameters to structs - context.Context // Context to carry objects to handlers - *libpod.Runtime // Where the real work happens - net.Listener // mux for routing HTTP API calls to libpod routines - context.CancelFunc // Stop APIServer - *time.Timer // Hold timer for sliding window - time.Duration // Duration of client access sliding window + http.Server // The HTTP work happens here + *schema.Decoder // Decoder for Query parameters to structs + context.Context // Context to carry objects to handlers + *libpod.Runtime // Where the real work happens + net.Listener // mux for routing HTTP API calls to libpod routines + context.CancelFunc // Stop APIServer + *time.Timer // Hold timer for sliding window + time.Duration // Duration of client access sliding window + ActiveConnections uint64 // Number of handlers holding a connection + TotalConnections uint64 // Number of connections handled + ConnectionCh chan int // Channel for signalling handler enter/exit } // Number of seconds to wait for next request, if exceeded shutdown server const ( DefaultServiceDuration = 300 * time.Second UnlimitedServiceDuration = 0 * time.Second + EnterHandler = 1 + ExitHandler = -1 + NOOPHandler = 0 ) // NewServer will create and configure a new API server with all defaults @@ -68,31 +74,19 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li Server: http.Server{ Handler: router, ReadHeaderTimeout: 20 * time.Second, - ReadTimeout: 20 * time.Second, - WriteTimeout: 2 * time.Minute, + IdleTimeout: duration, }, - Decoder: handlers.NewAPIDecoder(), - Context: nil, - Runtime: runtime, - Listener: *listener, - CancelFunc: nil, - Duration: duration, + Decoder: handlers.NewAPIDecoder(), + Runtime: runtime, + Listener: *listener, + Duration: duration, + ConnectionCh: make(chan int), } + server.Timer = time.AfterFunc(server.Duration, func() { - if err := server.Shutdown(); err != nil { - logrus.Errorf("unable to shutdown server: %q", err) - } + server.ConnectionCh <- NOOPHandler }) - ctx, cancelFn := context.WithCancel(context.Background()) - server.CancelFunc = cancelFn - - // TODO: Use ConnContext when ported to go 1.13 - ctx = context.WithValue(ctx, "decoder", server.Decoder) - ctx = context.WithValue(ctx, "runtime", runtime) - ctx = context.WithValue(ctx, "shutdownFunc", server.Shutdown) - server.Context = ctx - router.NotFoundHandler = http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { // We can track user errors... @@ -102,20 +96,19 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li ) for _, fn := range []func(*mux.Router) error{ - server.RegisterAuthHandlers, - server.RegisterContainersHandlers, - server.RegisterDistributionHandlers, + server.registerAuthHandlers, + server.registerContainersHandlers, + server.registerDistributionHandlers, server.registerExecHandlers, - server.RegisterEventsHandlers, + server.registerEventsHandlers, server.registerHealthCheckHandlers, server.registerImagesHandlers, server.registerInfoHandlers, - server.RegisterMonitorHandlers, + server.registerMonitorHandlers, server.registerPingHandlers, - server.RegisterPluginsHandlers, + server.registerPluginsHandlers, server.registerPodsHandlers, - server.RegisterSwaggerHandlers, - server.RegisterSwarmHandlers, + server.registerSwarmHandlers, server.registerSystemHandlers, server.registerVersionHandlers, server.registerVolumeHandlers, @@ -145,7 +138,41 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li // Serve starts responding to HTTP requests func (s *APIServer) Serve() error { - defer s.CancelFunc() + // stalker to count the connections. Should the timer expire it will shutdown the service. + go func() { + for { + select { + case delta := <-s.ConnectionCh: + // Always stop the current timer, things will change... + s.Timer.Stop() + switch delta { + case EnterHandler: + s.ActiveConnections += 1 + s.TotalConnections += 1 + case ExitHandler: + s.ActiveConnections -= 1 + if s.ActiveConnections == 0 { + // Server will be shutdown iff the timer expires before being reset or stopped + s.Timer = time.AfterFunc(s.Duration, func() { + if err := s.Shutdown(); err != nil { + logrus.Errorf("Failed to shutdown APIServer: %v", err) + os.Exit(1) + } + }) + } else { + s.Timer.Reset(s.Duration) + } + case NOOPHandler: + // push the check out another duration... + s.Timer.Reset(s.Duration) + default: + logrus.Errorf("ConnectionCh received unsupported input %d", delta) + } + default: + time.Sleep(1 * time.Second) + } + } + }() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) @@ -155,6 +182,7 @@ func (s *APIServer) Serve() error { err := s.Server.Serve(s.Listener) if err != nil && err != http.ErrServerClosed { errChan <- errors.Wrap(err, "Failed to start APIServer") + return } errChan <- nil }() @@ -171,27 +199,23 @@ func (s *APIServer) Serve() error { // Shutdown is a clean shutdown waiting on existing clients func (s *APIServer) Shutdown() error { - // Duration == 0 flags no auto-shutdown of server + // Duration == 0 flags no auto-shutdown of the server if s.Duration == 0 { + logrus.Debug("APIServer.Shutdown ignored as Duration == 0") return nil } + logrus.Debugf("APIServer.Shutdown called %v, conn %d/%d", time.Now(), s.ActiveConnections, s.TotalConnections) - // We're still in the sliding service window - if s.Timer.Stop() { - s.Timer.Reset(s.Duration) - return nil - } + // Gracefully shutdown server + ctx, cancel := context.WithTimeout(context.Background(), s.Duration) + defer cancel() - // We've been idle for the service window, really shutdown go func() { - err := s.Server.Shutdown(s.Context) + err := s.Server.Shutdown(ctx) if err != nil && err != context.Canceled { logrus.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error()) } }() - - // Wait for graceful shutdown vs. just killing connections and dropping data - <-s.Context.Done() return nil } diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go new file mode 100644 index 000000000..18b32335b --- /dev/null +++ b/pkg/bindings/containers/create.go @@ -0,0 +1,30 @@ +package containers + +import ( + "context" + "net/http" + "strings" + + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/specgen" + jsoniter "github.com/json-iterator/go" +) + +func CreateWithSpec(ctx context.Context, s specgen.SpecGenerator) (utils.ContainerCreateResponse, error) { + var ccr utils.ContainerCreateResponse + conn, err := bindings.GetConnectionFromContext(ctx) + if err != nil { + return ccr, err + } + specgenString, err := jsoniter.MarshalToString(s) + if err != nil { + return ccr, nil + } + stringReader := strings.NewReader(specgenString) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil) + if err != nil { + return ccr, err + } + return ccr, response.Process(&ccr) +} diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 182a39f6b..f71d55776 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -452,6 +452,7 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st var lastErr error var pausePid int + foundProcess := false for _, path := range paths { if !needNewNamespace { @@ -502,12 +503,16 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st } pausePid, err = strconv.Atoi(string(b[:n])) - if err == nil { + if err == nil && unix.Kill(pausePid, 0) == nil { + foundProcess = true lastErr = nil break } } } + if !foundProcess { + return BecomeRootInUserNS(pausePidPath) + } if lastErr != nil { return false, 0, lastErr } diff --git a/pkg/seccomp/seccomp.go b/pkg/seccomp/seccomp.go index dcf255378..eeba46a72 100644 --- a/pkg/seccomp/seccomp.go +++ b/pkg/seccomp/seccomp.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" ) -// ContianerImageLabel is the key of the image annotation embedding a seccomp +// ContainerImageLabel is the key of the image annotation embedding a seccomp // profile. const ContainerImageLabel = "io.containers.seccomp.profile" diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go index 5f39b6d0d..544c0020d 100644 --- a/pkg/spec/config_linux.go +++ b/pkg/spec/config_linux.go @@ -32,8 +32,8 @@ func Device(d *configs.Device) spec.LinuxDevice { } } -// devicesFromPath computes a list of devices -func devicesFromPath(g *generate.Generator, devicePath string) error { +// DevicesFromPath computes a list of devices +func DevicesFromPath(g *generate.Generator, devicePath string) error { devs := strings.Split(devicePath, ":") resolvedDevicePath := devs[0] // check if it is a symbolic link @@ -216,7 +216,7 @@ func getDevices(path string) ([]*configs.Device, error) { return out, nil } -func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error { +func addPrivilegedDevices(g *generate.Generator) error { hostDevices, err := getDevices("/dev") if err != nil { return err @@ -280,16 +280,16 @@ func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) { var lwds []spec.LinuxWeightDevice ret = bio for _, i := range c.Resources.BlkioWeightDevice { - wd, err := validateweightDevice(i) + wd, err := ValidateweightDevice(i) if err != nil { return ret, errors.Wrapf(err, "invalid values for blkio-weight-device") } - wdStat, err := getStatFromPath(wd.path) + wdStat, err := GetStatFromPath(wd.Path) if err != nil { - return ret, errors.Wrapf(err, "error getting stat from path %q", wd.path) + return ret, errors.Wrapf(err, "error getting stat from path %q", wd.Path) } lwd := spec.LinuxWeightDevice{ - Weight: &wd.weight, + Weight: &wd.Weight, } lwd.Major = int64(unix.Major(wdStat.Rdev)) lwd.Minor = int64(unix.Minor(wdStat.Rdev)) @@ -347,7 +347,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott if err != nil { return []spec.LinuxThrottleDevice{}, err } - ltdStat, err := getStatFromPath(t.path) + ltdStat, err := GetStatFromPath(t.path) if err != nil { return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path) } @@ -361,7 +361,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott return ltds, nil } -func getStatFromPath(path string) (unix.Stat_t, error) { +func GetStatFromPath(path string) (unix.Stat_t, error) { s := unix.Stat_t{} err := unix.Stat(path, &s) return s, err diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go index be3e7046d..568afde55 100644 --- a/pkg/spec/config_unsupported.go +++ b/pkg/spec/config_unsupported.go @@ -15,7 +15,7 @@ func addDevice(g *generate.Generator, device string) error { return errors.New("function not implemented") } -func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error { +func addPrivilegedDevices(g *generate.Generator) error { return errors.New("function not implemented") } @@ -27,7 +27,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott return nil, errors.New("function not implemented") } -func devicesFromPath(g *generate.Generator, devicePath string) error { +func DevicesFromPath(g *generate.Generator, devicePath string) error { return errors.New("function not implemented") } diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 8010be0d4..5011df496 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -126,6 +126,7 @@ type SecurityConfig struct { } // CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI +// swagger:model CreateConfig type CreateConfig struct { Annotations map[string]string Args []string @@ -386,6 +387,6 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l // AddPrivilegedDevices iterates through host devices and adds all // host devices to the spec -func (c *CreateConfig) AddPrivilegedDevices(g *generate.Generator) error { - return c.addPrivilegedDevices(g) +func AddPrivilegedDevices(g *generate.Generator) error { + return addPrivilegedDevices(g) } diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go index a5dfccdb9..38d93b87f 100644 --- a/pkg/spec/parse.go +++ b/pkg/spec/parse.go @@ -19,12 +19,12 @@ const Pod = "pod" // weightDevice is a structure that holds device:weight pair type weightDevice struct { - path string - weight uint16 + Path string + Weight uint16 } func (w *weightDevice) String() string { - return fmt.Sprintf("%s:%d", w.path, w.weight) + return fmt.Sprintf("%s:%d", w.Path, w.Weight) } // LinuxNS is a struct that contains namespace information @@ -59,9 +59,9 @@ func NS(s string) string { return "" } -// validateweightDevice validates that the specified string has a valid device-weight format +// ValidateweightDevice validates that the specified string has a valid device-weight format // for blkio-weight-device flag -func validateweightDevice(val string) (*weightDevice, error) { +func ValidateweightDevice(val string) (*weightDevice, error) { split := strings.SplitN(val, ":", 2) if len(split) != 2 { return nil, fmt.Errorf("bad format: %s", val) @@ -78,8 +78,8 @@ func validateweightDevice(val string) (*weightDevice, error) { } return &weightDevice{ - path: split[0], - weight: uint16(weight), + Path: split[0], + Weight: uint16(weight), }, nil } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index b2a152a2d..a4ae22efd 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -16,9 +16,9 @@ import ( "github.com/pkg/errors" ) -const cpuPeriod = 100000 +const CpuPeriod = 100000 -func getAvailableGids() (int64, error) { +func GetAvailableGids() (int64, error) { idMap, err := user.ParseIDMapFile("/proc/self/gid_map") if err != nil { return 0, err @@ -80,7 +80,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } gid5Available := true if isRootless { - nGids, err := getAvailableGids() + nGids, err := GetAvailableGids() if err != nil { return nil, err } @@ -197,8 +197,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM addedResources = true } if config.Resources.CPUs != 0 { - g.SetLinuxResourcesCPUPeriod(cpuPeriod) - g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * cpuPeriod)) + g.SetLinuxResourcesCPUPeriod(CpuPeriod) + g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * CpuPeriod)) addedResources = true } if config.Resources.CPURtRuntime != 0 { @@ -223,12 +223,12 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM // If privileged, we need to add all the host devices to the // spec. We do not add the user provided ones because we are // already adding them all. - if err := config.AddPrivilegedDevices(&g); err != nil { + if err := AddPrivilegedDevices(&g); err != nil { return nil, err } } else { for _, devicePath := range config.Devices { - if err := devicesFromPath(&g, devicePath); err != nil { + if err := DevicesFromPath(&g, devicePath); err != nil { return nil, err } } @@ -241,23 +241,35 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } // SECURITY OPTS + var runtimeConfig *libpodconfig.Config + + if runtime != nil { + runtimeConfig, err = runtime.GetConfig() + if err != nil { + return nil, err + } + } + g.SetProcessNoNewPrivileges(config.Security.NoNewPrivs) if !config.Security.Privileged { g.SetProcessApparmorProfile(config.Security.ApparmorProfile) } - blockAccessToKernelFilesystems(config, &g) - - var runtimeConfig *libpodconfig.Config - - if runtime != nil { - runtimeConfig, err = runtime.GetConfig() - if err != nil { + // Unless already set via the CLI, check if we need to disable process + // labels or set the defaults. + if len(config.Security.LabelOpts) == 0 && runtimeConfig != nil { + if !runtimeConfig.EnableLabeling { + // Disabled in the config. + config.Security.LabelOpts = append(config.Security.LabelOpts, "disable") + } else if err := config.Security.SetLabelOpts(runtime, &config.Pid, &config.Ipc); err != nil { + // Defaults! return nil, err } } + BlockAccessToKernelFilesystems(config.Security.Privileged, config.Pid.PidMode.IsHost(), &g) + // RESOURCES - PIDS if config.Resources.PidsLimit > 0 { // if running on rootless on a cgroupv1 machine or using the cgroupfs manager, pids @@ -320,9 +332,9 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } // BIND MOUNTS - configSpec.Mounts = supercedeUserMounts(userMounts, configSpec.Mounts) + configSpec.Mounts = SupercedeUserMounts(userMounts, configSpec.Mounts) // Process mounts to ensure correct options - finalMounts, err := initFSMounts(configSpec.Mounts) + finalMounts, err := InitFSMounts(configSpec.Mounts) if err != nil { return nil, err } @@ -404,8 +416,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM return configSpec, nil } -func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) { - if !config.Security.Privileged { +func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) { + if !privileged { for _, mp := range []string{ "/proc/acpi", "/proc/kcore", @@ -421,7 +433,7 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) g.AddLinuxMaskedPaths(mp) } - if config.Pid.PidMode.IsHost() && rootless.IsRootless() { + if pidModeIsHost && rootless.IsRootless() { return } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index 0e2098c1d..e37fa2451 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -825,7 +825,7 @@ func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, err // TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by // one mount, and we already have /tmp/a and /tmp/b, should we remove // the /tmp/a and /tmp/b mounts in favor of the more general /tmp? -func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount { +func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount { if len(mounts) > 0 { // If we have overlappings mounts, remove them from the spec in favor of // the user-added volume mounts @@ -854,7 +854,7 @@ func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M } // Ensure mount options on all mounts are correct -func initFSMounts(inputMounts []spec.Mount) ([]spec.Mount, error) { +func InitFSMounts(inputMounts []spec.Mount) ([]spec.Mount, error) { // We need to look up mounts so we can figure out the proper mount flags // to apply. systemMounts, err := pmount.GetMounts() diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/config_linux_cgo.go new file mode 100644 index 000000000..6f547a40d --- /dev/null +++ b/pkg/specgen/config_linux_cgo.go @@ -0,0 +1,62 @@ +// +build linux,cgo + +package specgen + +import ( + "context" + "io/ioutil" + + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/seccomp" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + goSeccomp "github.com/seccomp/containers-golang" + "github.com/sirupsen/logrus" +) + +func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { + var seccompConfig *spec.LinuxSeccomp + var err error + + scp, err := seccomp.LookupPolicy(s.SeccompPolicy) + if err != nil { + return nil, err + } + + if scp == seccomp.PolicyImage { + labels, err := img.Labels(context.Background()) + if err != nil { + return nil, err + } + imagePolicy := labels[seccomp.ContainerImageLabel] + if len(imagePolicy) < 1 { + return nil, errors.New("no seccomp policy defined by image") + } + logrus.Debug("Loading seccomp profile from the security config") + seccompConfig, err = goSeccomp.LoadProfile(imagePolicy, configSpec) + if err != nil { + return nil, errors.Wrap(err, "loading seccomp profile failed") + } + return seccompConfig, nil + } + + if s.SeccompProfilePath != "" { + logrus.Debugf("Loading seccomp profile from %q", s.SeccompProfilePath) + seccompProfile, err := ioutil.ReadFile(s.SeccompProfilePath) + if err != nil { + return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", s.SeccompProfilePath) + } + seccompConfig, err = goSeccomp.LoadProfile(string(seccompProfile), configSpec) + if err != nil { + return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath) + } + } else { + logrus.Debug("Loading default seccomp profile") + seccompConfig, err = goSeccomp.GetDefaultProfile(configSpec) + if err != nil { + return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath) + } + } + + return seccompConfig, nil +} diff --git a/pkg/specgen/config_linux_nocgo.go b/pkg/specgen/config_linux_nocgo.go new file mode 100644 index 000000000..fc0c58c37 --- /dev/null +++ b/pkg/specgen/config_linux_nocgo.go @@ -0,0 +1,11 @@ +// +build linux,!cgo + +package specgen + +import ( + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { + return nil, nil +} diff --git a/pkg/specgen/config_unsupported.go b/pkg/specgen/config_unsupported.go new file mode 100644 index 000000000..5d24ac39c --- /dev/null +++ b/pkg/specgen/config_unsupported.go @@ -0,0 +1,12 @@ +// +build !linux + +package specgen + +import ( + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" +) + +func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { + return nil, errors.New("function not supported on non-linux OS's") +} diff --git a/pkg/specgen/create.go b/pkg/specgen/create.go new file mode 100644 index 000000000..c8fee5f05 --- /dev/null +++ b/pkg/specgen/create.go @@ -0,0 +1,187 @@ +package specgen + +import ( + "context" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/config" + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "os" +) + +// MakeContainer creates a container based on the SpecGenerator +func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) { + var pod *libpod.Pod + if err := s.validate(rt); err != nil { + return nil, errors.Wrap(err, "invalid config provided") + } + rtc, err := rt.GetConfig() + if err != nil { + return nil, err + } + + options, err := s.createContainerOptions(rt, pod) + if err != nil { + return nil, err + } + + podmanPath, err := os.Executable() + if err != nil { + return nil, err + } + options = append(options, s.createExitCommandOption(rtc, podmanPath)) + newImage, err := rt.ImageRuntime().NewFromLocal(s.Image) + if err != nil { + return nil, err + } + + // TODO mheon wants to talk with Dan about this + useImageVolumes := s.ImageVolumeMode == "bind" + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, useImageVolumes)) + + runtimeSpec, err := s.toOCISpec(rt, newImage) + if err != nil { + return nil, err + } + return rt.NewContainer(context.Background(), runtimeSpec, options...) +} + +func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { + var options []libpod.CtrCreateOption + var err error + + if s.Stdin { + options = append(options, libpod.WithStdin()) + } + if len(s.Systemd) > 0 { + options = append(options, libpod.WithSystemd()) + } + if len(s.Name) > 0 { + logrus.Debugf("setting container name %s", s.Name) + options = append(options, libpod.WithName(s.Name)) + } + if s.Pod != "" { + logrus.Debugf("adding container to pod %s", s.Pod) + options = append(options, rt.WithPod(pod)) + } + destinations := []string{} + // // Take all mount and named volume destinations. + for _, mount := range s.Mounts { + destinations = append(destinations, mount.Destination) + } + for _, volume := range s.Volumes { + destinations = append(destinations, volume.Dest) + } + options = append(options, libpod.WithUserVolumes(destinations)) + + if len(s.Volumes) != 0 { + options = append(options, libpod.WithNamedVolumes(s.Volumes)) + } + + if len(s.Command) != 0 { + options = append(options, libpod.WithCommand(s.Command)) + } + + options = append(options, libpod.WithEntrypoint(s.Entrypoint)) + if s.StopSignal != nil { + options = append(options, libpod.WithStopSignal(*s.StopSignal)) + } + if s.StopTimeout != nil { + options = append(options, libpod.WithStopTimeout(*s.StopTimeout)) + } + if s.LogConfiguration != nil { + if len(s.LogConfiguration.Path) > 0 { + options = append(options, libpod.WithLogPath(s.LogConfiguration.Path)) + } + if len(s.LogConfiguration.Options) > 0 && s.LogConfiguration.Options["tag"] != "" { + // Note: I'm really guessing here. + options = append(options, libpod.WithLogTag(s.LogConfiguration.Options["tag"])) + } + + if len(s.LogConfiguration.Driver) > 0 { + options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver)) + } + } + + // Security options + if len(s.SelinuxOpts) > 0 { + options = append(options, libpod.WithSecLabels(s.SelinuxOpts)) + } + options = append(options, libpod.WithPrivileged(s.Privileged)) + + // Get namespace related options + namespaceOptions, err := s.generateNamespaceContainerOpts(rt) + if err != nil { + return nil, err + } + options = append(options, namespaceOptions...) + + // TODO NetworkNS still needs to be done! + if len(s.ConmonPidFile) > 0 { + options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile)) + } + options = append(options, libpod.WithLabels(s.Labels)) + if s.ShmSize != nil { + options = append(options, libpod.WithShmSize(*s.ShmSize)) + } + if s.Rootfs != "" { + options = append(options, libpod.WithRootFS(s.Rootfs)) + } + // Default used if not overridden on command line + + if s.RestartPolicy != "" { + if s.RestartPolicy == "unless-stopped" { + return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") + } + if s.RestartRetries != nil { + options = append(options, libpod.WithRestartRetries(*s.RestartRetries)) + } + options = append(options, libpod.WithRestartPolicy(s.RestartPolicy)) + } + + if s.ContainerHealthCheckConfig.HealthConfig != nil { + options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig)) + logrus.Debugf("New container has a health check") + } + return options, nil +} + +func (s *SpecGenerator) createExitCommandOption(config *config.Config, podmanPath string) libpod.CtrCreateOption { + // We need a cleanup process for containers in the current model. + // But we can't assume that the caller is Podman - it could be another + // user of the API. + // As such, provide a way to specify a path to Podman, so we can + // still invoke a cleanup process. + + command := []string{podmanPath, + "--root", config.StorageConfig.GraphRoot, + "--runroot", config.StorageConfig.RunRoot, + "--log-level", logrus.GetLevel().String(), + "--cgroup-manager", config.CgroupManager, + "--tmpdir", config.TmpDir, + } + if config.OCIRuntime != "" { + command = append(command, []string{"--runtime", config.OCIRuntime}...) + } + if config.StorageConfig.GraphDriverName != "" { + command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) + } + for _, opt := range config.StorageConfig.GraphDriverOptions { + command = append(command, []string{"--storage-opt", opt}...) + } + if config.EventsLogger != "" { + command = append(command, []string{"--events-backend", config.EventsLogger}...) + } + + // TODO Mheon wants to leave this for now + //if s.sys { + // command = append(command, "--syslog", "true") + //} + command = append(command, []string{"container", "cleanup"}...) + + if s.Remove { + command = append(command, "--rm") + } + return libpod.WithExitCommand(command) +} diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go new file mode 100644 index 000000000..025cb31e0 --- /dev/null +++ b/pkg/specgen/namespaces.go @@ -0,0 +1,467 @@ +package specgen + +import ( + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/capabilities" + "github.com/cri-o/ocicni/pkg/ocicni" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +type NamespaceMode string + +const ( + // Host means the the namespace is derived from + // the host + Host NamespaceMode = "host" + // Path is the path to a namespace + Path NamespaceMode = "path" + // FromContainer means namespace is derived from a + // different container + FromContainer NamespaceMode = "container" + // FromPod indicates the namespace is derived from a pod + FromPod NamespaceMode = "pod" + // Private indicates the namespace is private + Private NamespaceMode = "private" + // NoNetwork indicates no network namespace should + // be joined. loopback should still exists + NoNetwork NamespaceMode = "none" + // Bridge indicates that a CNI network stack + // should be used + Bridge NamespaceMode = "bridge" + // Slirp indicates that a slirp4ns network stack should + // be used + Slirp NamespaceMode = "slirp4ns" +) + +// Namespace describes the namespace +type Namespace struct { + NSMode NamespaceMode `json:"nsmode,omitempty"` + Value string `json:"string,omitempty"` +} + +// IsHost returns a bool if the namespace is host based +func (n *Namespace) IsHost() bool { + return n.NSMode == Host +} + +// IsPath indicates via bool if the namespace is based on a path +func (n *Namespace) IsPath() bool { + return n.NSMode == Path +} + +// IsContainer indicates via bool if the namespace is based on a container +func (n *Namespace) IsContainer() bool { + return n.NSMode == FromContainer +} + +// IsPod indicates via bool if the namespace is based on a pod +func (n *Namespace) IsPod() bool { + return n.NSMode == FromPod +} + +// IsPrivate indicates the namespace is private +func (n *Namespace) IsPrivate() bool { + return n.NSMode == Private +} + +// validate perform simple validation on the namespace to make sure it is not +// invalid from the get-go +func (n *Namespace) validate() error { + if n == nil { + return nil + } + switch n.NSMode { + case Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp: + break + default: + return errors.Errorf("invalid network %q", n.NSMode) + } + // Path and From Container MUST have a string value set + if n.NSMode == Path || n.NSMode == FromContainer { + if len(n.Value) < 1 { + return errors.Errorf("namespace mode %s requires a value", n.NSMode) + } + } else { + // All others must NOT set a string value + if len(n.Value) > 0 { + return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode) + } + } + return nil +} + +func (s *SpecGenerator) generateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + var portBindings []ocicni.PortMapping + options := make([]libpod.CtrCreateOption, 0) + + // Cgroups + switch { + case s.CgroupNS.IsPrivate(): + ns := s.CgroupNS.Value + if _, err := os.Stat(ns); err != nil { + return nil, err + } + case s.CgroupNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value) + } + options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) + // TODO + //default: + // return nil, errors.New("cgroup name only supports private and container") + } + + if s.CgroupParent != "" { + options = append(options, libpod.WithCgroupParent(s.CgroupParent)) + } + + if s.CgroupsMode != "" { + options = append(options, libpod.WithCgroupsMode(s.CgroupsMode)) + } + + // ipc + switch { + case s.IpcNS.IsHost(): + options = append(options, libpod.WithShmDir("/dev/shm")) + case s.IpcNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.IpcNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value) + } + options = append(options, libpod.WithIPCNSFrom(connectedCtr)) + options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) + } + + // pid + if s.PidNS.IsContainer() { + connectedCtr, err := rt.LookupContainer(s.PidNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value) + } + options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + } + + // uts + switch { + case s.UtsNS.IsPod(): + connectedPod, err := rt.LookupPod(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value) + } + options = append(options, libpod.WithUTSNSFromPod(connectedPod)) + case s.UtsNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value) + } + + options = append(options, libpod.WithUTSNSFrom(connectedCtr)) + } + + if s.UseImageHosts { + options = append(options, libpod.WithUseImageHosts()) + } else if len(s.HostAdd) > 0 { + options = append(options, libpod.WithHosts(s.HostAdd)) + } + + // User + + switch { + case s.UserNS.IsPath(): + ns := s.UserNS.Value + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined user namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + if s.IDMappings != nil { + options = append(options, libpod.WithIDMappings(*s.IDMappings)) + } + case s.UserNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.UserNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value) + } + options = append(options, libpod.WithUserNSFrom(connectedCtr)) + default: + if s.IDMappings != nil { + options = append(options, libpod.WithIDMappings(*s.IDMappings)) + } + } + + options = append(options, libpod.WithUser(s.User)) + options = append(options, libpod.WithGroups(s.Groups)) + + if len(s.PortMappings) > 0 { + portBindings = s.PortMappings + } + + switch { + case s.NetNS.IsPath(): + ns := s.NetNS.Value + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined network namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + case s.NetNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.NetNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value) + } + options = append(options, libpod.WithNetNSFrom(connectedCtr)) + case !s.NetNS.IsHost() && s.NetNS.NSMode != NoNetwork: + postConfigureNetNS := !s.UserNS.IsHost() + options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks)) + } + + if len(s.DNSSearch) > 0 { + options = append(options, libpod.WithDNSSearch(s.DNSSearch)) + } + if len(s.DNSServer) > 0 { + // TODO I'm not sure how we are going to handle this given the input + if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" { + options = append(options, libpod.WithUseImageResolvConf()) + } else { + var dnsServers []string + for _, d := range s.DNSServer { + dnsServers = append(dnsServers, d.String()) + } + options = append(options, libpod.WithDNS(dnsServers)) + } + } + if len(s.DNSOption) > 0 { + options = append(options, libpod.WithDNSOption(s.DNSOption)) + } + if s.StaticIP != nil { + options = append(options, libpod.WithStaticIP(*s.StaticIP)) + } + + if s.StaticMAC != nil { + options = append(options, libpod.WithStaticMAC(*s.StaticMAC)) + } + return options, nil +} + +func (s *SpecGenerator) pidConfigureGenerator(g *generate.Generator) error { + if s.PidNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value) + } + if s.PidNS.IsHost() { + return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) + } + if s.PidNS.IsContainer() { + logrus.Debugf("using container %s pidmode", s.PidNS.Value) + } + if s.PidNS.IsPod() { + logrus.Debug("using pod pidmode") + } + return nil +} + +func (s *SpecGenerator) utsConfigureGenerator(g *generate.Generator, runtime *libpod.Runtime) error { + hostname := s.Hostname + var err error + if hostname == "" { + switch { + case s.UtsNS.IsContainer(): + utsCtr, err := runtime.GetContainer(s.UtsNS.Value) + if err != nil { + return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) + } + hostname = utsCtr.Hostname() + case s.NetNS.IsHost() || s.UtsNS.IsHost(): + hostname, err = os.Hostname() + if err != nil { + return errors.Wrap(err, "unable to retrieve hostname of the host") + } + default: + logrus.Debug("No hostname set; container's hostname will default to runtime default") + } + } + g.RemoveHostname() + if s.Hostname != "" || !s.UtsNS.IsHost() { + // Set the hostname in the OCI configuration only + // if specified by the user or if we are creating + // a new UTS namespace. + g.SetHostname(hostname) + } + g.AddProcessEnv("HOSTNAME", hostname) + + if s.UtsNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value) + } + if s.UtsNS.IsHost() { + return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) + } + if s.UtsNS.IsContainer() { + logrus.Debugf("using container %s utsmode", s.UtsNS.Value) + } + return nil +} + +func (s *SpecGenerator) ipcConfigureGenerator(g *generate.Generator) error { + if s.IpcNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value) + } + if s.IpcNS.IsHost() { + return g.RemoveLinuxNamespace(s.IpcNS.Value) + } + if s.IpcNS.IsContainer() { + logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value) + } + return nil +} + +func (s *SpecGenerator) cgroupConfigureGenerator(g *generate.Generator) error { + if s.CgroupNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value) + } + if s.CgroupNS.IsHost() { + return g.RemoveLinuxNamespace(s.CgroupNS.Value) + } + if s.CgroupNS.IsPrivate() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") + } + if s.CgroupNS.IsContainer() { + logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value) + } + return nil +} + +func (s *SpecGenerator) networkConfigureGenerator(g *generate.Generator) error { + switch { + case s.NetNS.IsHost(): + logrus.Debug("Using host netmode") + if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { + return err + } + + case s.NetNS.NSMode == NoNetwork: + logrus.Debug("Using none netmode") + case s.NetNS.NSMode == Bridge: + logrus.Debug("Using bridge netmode") + case s.NetNS.IsContainer(): + logrus.Debugf("using container %s netmode", s.NetNS.Value) + case s.NetNS.IsPath(): + logrus.Debug("Using ns netmode") + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { + return err + } + case s.NetNS.IsPod(): + logrus.Debug("Using pod netmode, unless pod is not sharing") + case s.NetNS.NSMode == Slirp: + logrus.Debug("Using slirp4netns netmode") + default: + return errors.Errorf("unknown network mode") + } + + if g.Config.Annotations == nil { + g.Config.Annotations = make(map[string]string) + } + + if s.PublishImagePorts { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + } else { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse + } + + return nil +} + +func (s *SpecGenerator) userConfigureGenerator(g *generate.Generator) error { + if s.UserNS.IsPath() { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil { + return err + } + // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping + g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) + g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) + } + + if s.IDMappings != nil { + if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + } + for _, uidmap := range s.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range s.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + } + return nil +} + +func (s *SpecGenerator) securityConfigureGenerator(g *generate.Generator, newImage *image.Image) error { + // HANDLE CAPABILITIES + // NOTE: Must happen before SECCOMP + if s.Privileged { + g.SetupPrivileged(true) + } + + useNotRoot := func(user string) bool { + if user == "" || user == "root" || user == "0" { + return false + } + return true + } + configSpec := g.Config + var err error + var caplist []string + bounding := configSpec.Process.Capabilities.Bounding + if useNotRoot(s.User) { + configSpec.Process.Capabilities.Bounding = caplist + } + caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) + if err != nil { + return err + } + + configSpec.Process.Capabilities.Bounding = caplist + configSpec.Process.Capabilities.Permitted = caplist + configSpec.Process.Capabilities.Inheritable = caplist + configSpec.Process.Capabilities.Effective = caplist + configSpec.Process.Capabilities.Ambient = caplist + if useNotRoot(s.User) { + caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) + if err != nil { + return err + } + } + configSpec.Process.Capabilities.Bounding = caplist + + // HANDLE SECCOMP + if s.SeccompProfilePath != "unconfined" { + seccompConfig, err := s.getSeccompConfig(configSpec, newImage) + if err != nil { + return err + } + configSpec.Linux.Seccomp = seccompConfig + } + + // Clear default Seccomp profile from Generator for privileged containers + if s.SeccompProfilePath == "unconfined" || s.Privileged { + configSpec.Linux.Seccomp = nil + } + + g.SetRootReadonly(s.ReadOnlyFilesystem) + for sysctlKey, sysctlVal := range s.Sysctl { + g.AddLinuxSysctl(sysctlKey, sysctlVal) + } + + return nil +} diff --git a/pkg/specgen/oci.go b/pkg/specgen/oci.go new file mode 100644 index 000000000..2523f21b3 --- /dev/null +++ b/pkg/specgen/oci.go @@ -0,0 +1,260 @@ +package specgen + +import ( + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/rootless" + createconfig "github.com/containers/libpod/pkg/spec" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" +) + +func (s *SpecGenerator) toOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { + var ( + inUserNS bool + ) + cgroupPerm := "ro" + g, err := generate.New("linux") + if err != nil { + return nil, err + } + // Remove the default /dev/shm mount to ensure we overwrite it + g.RemoveMount("/dev/shm") + g.HostSpecific = true + addCgroup := true + canMountSys := true + + isRootless := rootless.IsRootless() + if isRootless { + inUserNS = true + } + if !s.UserNS.IsHost() { + if s.UserNS.IsContainer() || s.UserNS.IsPath() { + inUserNS = true + } + if s.UserNS.IsPrivate() { + inUserNS = true + } + } + if inUserNS && s.NetNS.IsHost() { + canMountSys = false + } + + if s.Privileged && canMountSys { + cgroupPerm = "rw" + g.RemoveMount("/sys") + sysMnt := spec.Mount{ + Destination: "/sys", + Type: "sysfs", + Source: "sysfs", + Options: []string{"rprivate", "nosuid", "noexec", "nodev", "rw"}, + } + g.AddMount(sysMnt) + } else if !canMountSys { + addCgroup = false + g.RemoveMount("/sys") + r := "ro" + if s.Privileged { + r = "rw" + } + sysMnt := spec.Mount{ + Destination: "/sys", + Type: "bind", // should we use a constant for this, like createconfig? + Source: "/sys", + Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, + } + g.AddMount(sysMnt) + if !s.Privileged && isRootless { + g.AddLinuxMaskedPaths("/sys/kernel") + } + } + gid5Available := true + if isRootless { + nGids, err := createconfig.GetAvailableGids() + if err != nil { + return nil, err + } + gid5Available = nGids >= 5 + } + // When using a different user namespace, check that the GID 5 is mapped inside + // the container. + if gid5Available && (s.IDMappings != nil && len(s.IDMappings.GIDMap) > 0) { + mappingFound := false + for _, r := range s.IDMappings.GIDMap { + if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size { + mappingFound = true + break + } + } + if !mappingFound { + gid5Available = false + } + + } + if !gid5Available { + // If we have no GID mappings, the gid=5 default option would fail, so drop it. + g.RemoveMount("/dev/pts") + devPts := spec.Mount{ + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}, + } + g.AddMount(devPts) + } + + if inUserNS && s.IpcNS.IsHost() { + g.RemoveMount("/dev/mqueue") + devMqueue := spec.Mount{ + Destination: "/dev/mqueue", + Type: "bind", // constant ? + Source: "/dev/mqueue", + Options: []string{"bind", "nosuid", "noexec", "nodev"}, + } + g.AddMount(devMqueue) + } + if inUserNS && s.PidNS.IsHost() { + g.RemoveMount("/proc") + procMount := spec.Mount{ + Destination: "/proc", + Type: createconfig.TypeBind, + Source: "/proc", + Options: []string{"rbind", "nosuid", "noexec", "nodev"}, + } + g.AddMount(procMount) + } + + if addCgroup { + cgroupMnt := spec.Mount{ + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Source: "cgroup", + Options: []string{"rprivate", "nosuid", "noexec", "nodev", "relatime", cgroupPerm}, + } + g.AddMount(cgroupMnt) + } + g.SetProcessCwd(s.WorkDir) + g.SetProcessArgs(s.Command) + g.SetProcessTerminal(s.Terminal) + + for key, val := range s.Annotations { + g.AddAnnotation(key, val) + } + g.AddProcessEnv("container", "podman") + + g.Config.Linux.Resources = s.ResourceLimits + + // Devices + if s.Privileged { + // If privileged, we need to add all the host devices to the + // spec. We do not add the user provided ones because we are + // already adding them all. + if err := createconfig.AddPrivilegedDevices(&g); err != nil { + return nil, err + } + } else { + for _, device := range s.Devices { + if err := createconfig.DevicesFromPath(&g, device.Path); err != nil { + return nil, err + } + } + } + + // SECURITY OPTS + g.SetProcessNoNewPrivileges(s.NoNewPrivileges) + + if !s.Privileged { + g.SetProcessApparmorProfile(s.ApparmorProfile) + } + + createconfig.BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g) + + for name, val := range s.Env { + g.AddProcessEnv(name, val) + } + + // TODO rlimits and ulimits needs further refinement by someone more + // familiar with the code. + //if err := addRlimits(config, &g); err != nil { + // return nil, err + //} + + // NAMESPACES + + if err := s.pidConfigureGenerator(&g); err != nil { + return nil, err + } + + if err := s.userConfigureGenerator(&g); err != nil { + return nil, err + } + + if err := s.networkConfigureGenerator(&g); err != nil { + return nil, err + } + + if err := s.utsConfigureGenerator(&g, rt); err != nil { + return nil, err + } + + if err := s.ipcConfigureGenerator(&g); err != nil { + return nil, err + } + + if err := s.cgroupConfigureGenerator(&g); err != nil { + return nil, err + } + configSpec := g.Config + + if err := s.securityConfigureGenerator(&g, newImage); err != nil { + return nil, err + } + + // BIND MOUNTS + configSpec.Mounts = createconfig.SupercedeUserMounts(s.Mounts, configSpec.Mounts) + // Process mounts to ensure correct options + finalMounts, err := createconfig.InitFSMounts(configSpec.Mounts) + if err != nil { + return nil, err + } + configSpec.Mounts = finalMounts + + // Add annotations + if configSpec.Annotations == nil { + configSpec.Annotations = make(map[string]string) + } + + // TODO cidfile is not in specgen; when wiring up cli, we will need to move this out of here + // leaving as a reminder + //if config.CidFile != "" { + // configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile + //} + + if s.Remove { + configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue + } else { + configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse + } + + if len(s.VolumesFrom) > 0 { + configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",") + } + + if s.Privileged { + configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue + } else { + configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse + } + + // TODO Init might not make it into the specgen and therefore is not available here. We should deal + // with this when we wire up the CLI; leaving as a reminder + //if s.Init { + // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue + //} else { + // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse + //} + + return configSpec, nil +} diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index e22ee598f..e1dfe4dc5 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -2,24 +2,28 @@ package specgen import ( "net" + "syscall" "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" ) -// TODO -// mheon provided this an off the cuff suggestion. Adding it here to retain -// for history as we implement it. When this struct is implemented, we need -// to remove the nolints. -type Namespace struct { - isHost bool //nolint - isPath string //nolint - isContainer string //nolint - isPod bool //nolint +// LogConfig describes the logging characteristics for a container +type LogConfig struct { + // LogDriver is the container's log driver. + // Optional. + Driver string `json:"driver,omitempty"` + // LogPath is the path the container's logs will be stored at. + // Only available if LogDriver is set to "json-file" or "k8s-file". + // Optional. + Path string `json:"path,omitempty"` + // A set of options to accompany the log driver. + // Optional. + Options map[string]string `json:"options,omitempty"` } // ContainerBasicConfig contains the basic parts of a container. @@ -62,7 +66,7 @@ type ContainerBasicConfig struct { // If not provided, the default, SIGTERM, will be used. // Will conflict with Systemd if Systemd is set to "true" or "always". // Optional. - StopSignal *uint `json:"stop_signal,omitempty"` + StopSignal *syscall.Signal `json:"stop_signal,omitempty"` // StopTimeout is a timeout between the container's stop signal being // sent and SIGKILL being sent. // If not provided, the default will be used. @@ -70,13 +74,10 @@ type ContainerBasicConfig struct { // instead. // Optional. StopTimeout *uint `json:"stop_timeout,omitempty"` - // LogDriver is the container's log driver. - // Optional. - LogDriver string `json:"log_driver,omitempty"` - // LogPath is the path the container's logs will be stored at. - // Only available if LogDriver is set to "json-file" or "k8s-file". - // Optional. - LogPath string `json:"log_path,omitempty"` + // LogConfiguration describes the logging for a container including + // driver, path, and options. + // Optional + LogConfiguration *LogConfig `json:"log_configuration,omitempty"` // ConmonPidFile is a path at which a PID file for Conmon will be // placed. // If not given, a default location will be used. @@ -111,12 +112,10 @@ type ContainerBasicConfig struct { // Namespace is the libpod namespace the container will be placed in. // Optional. Namespace string `json:"namespace,omitempty"` - // PidNS is the container's PID namespace. // It defaults to private. // Mandatory. PidNS Namespace `json:"pidns,omitempty"` - // UtsNS is the container's UTS namespace. // It defaults to private. // Must be set to Private to set Hostname. @@ -128,6 +127,11 @@ type ContainerBasicConfig struct { // Conflicts with UtsNS if UtsNS is not set to private. // Optional. Hostname string `json:"hostname,omitempty"` + // Sysctl sets kernel parameters for the container + Sysctl map[string]string `json:"sysctl,omitempty"` + // Remove indicates if the container should be removed once it has been started + // and exits + Remove bool `json:"remove"` } // ContainerStorageConfig contains information on the storage configuration of a @@ -175,7 +179,7 @@ type ContainerStorageConfig struct { // Mandatory. IpcNS Namespace `json:"ipcns,omitempty"` // ShmSize is the size of the tmpfs to mount in at /dev/shm, in bytes. - // Conflicts with ShmSize if ShmSize is not private. + // Conflicts with ShmSize if IpcNS is not private. // Optional. ShmSize *int64 `json:"shm_size,omitempty"` // WorkDir is the container's working directory. @@ -234,6 +238,9 @@ type ContainerSecurityConfig struct { // will use. // Optional. ApparmorProfile string `json:"apparmor_profile,omitempty"` + // SeccompPolicy determines which seccomp profile gets applied + // the container. valid values: empty,default,image + SeccompPolicy string `json:"seccomp_policy,omitempty"` // SeccompProfilePath is the path to a JSON file containing the // container's Seccomp profile. // If not specified, no Seccomp profile will be used. @@ -252,7 +259,10 @@ type ContainerSecurityConfig struct { // IDMappings are UID and GID mappings that will be used by user // namespaces. // Required if UserNS is private. - IDMappings storage.IDMappingOptions `json:"idmappings,omitempty"` + IDMappings *storage.IDMappingOptions `json:"idmappings,omitempty"` + // ReadOnlyFilesystem indicates that everything will be mounted + // as read-only + ReadOnlyFilesystem bool `json:"read_only_filesystem,omittempty"` } // ContainerCgroupConfig contains configuration information about a container's @@ -260,16 +270,13 @@ type ContainerSecurityConfig struct { type ContainerCgroupConfig struct { // CgroupNS is the container's cgroup namespace. // It defaults to private. - // Conflicts with NoCgroups if not set to host. // Mandatory. CgroupNS Namespace `json:"cgroupns,omitempty"` - // NoCgroups indicates that the container should not create CGroups. - // Conflicts with CgroupParent and CgroupNS if CgroupNS is not set to - // host. - NoCgroups bool `json:"no_cgroups,omitempty"` + // CgroupsMode sets a policy for how cgroups will be created in the + // container, including the ability to disable creation entirely. + CgroupsMode string `json:"cgroups_mode,omitempty"` // CgroupParent is the container's CGroup parent. // If not set, the default for the current cgroup driver will be used. - // Conflicts with NoCgroups. // Optional. CgroupParent string `json:"cgroup_parent,omitempty"` } @@ -348,7 +355,7 @@ type ContainerNetworkConfig struct { // ContainerResourceConfig contains information on container resource limits. type ContainerResourceConfig struct { - // ResourceLimits are resource limits to apply to the container. + // ResourceLimits are resource limits to apply to the container., // Can only be set as root on cgroups v1 systems, but can be set as // rootless as well for cgroups v2. // Optional. @@ -365,11 +372,12 @@ type ContainerResourceConfig struct { // ContainerHealthCheckConfig describes a container healthcheck with attributes // like command, retries, interval, start period, and timeout. type ContainerHealthCheckConfig struct { - HealthConfig manifest.Schema2HealthConfig `json:"healthconfig,omitempty"` + HealthConfig *manifest.Schema2HealthConfig `json:"healthconfig,omitempty"` } // SpecGenerator creates an OCI spec and Libpod configuration options to create // a container based on the given configuration. +// swagger:model SpecGenerator type SpecGenerator struct { ContainerBasicConfig ContainerStorageConfig @@ -381,19 +389,24 @@ type SpecGenerator struct { } // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs -func NewSpecGenerator(image, rootfs *string) (*SpecGenerator, error) { - _ = image - _ = rootfs - return &SpecGenerator{}, define.ErrNotImplemented -} - -// Validate verifies that the given SpecGenerator is valid and satisfies required -// input for creating a container. -func (s *SpecGenerator) Validate() error { - return define.ErrNotImplemented +func NewSpecGenerator(image string) *SpecGenerator { + net := ContainerNetworkConfig{ + NetNS: Namespace{ + NSMode: Bridge, + }, + } + csc := ContainerStorageConfig{Image: image} + if rootless.IsRootless() { + net.NetNS.NSMode = Slirp + } + return &SpecGenerator{ + ContainerStorageConfig: csc, + ContainerNetworkConfig: net, + } } -// MakeContainer creates a container based on the SpecGenerator -func (s *SpecGenerator) MakeContainer() (*libpod.Container, error) { - return nil, define.ErrNotImplemented +// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs +func NewSpecGeneratorWithRootfs(rootfs string) *SpecGenerator { + csc := ContainerStorageConfig{Rootfs: rootfs} + return &SpecGenerator{ContainerStorageConfig: csc} } diff --git a/pkg/specgen/validate.go b/pkg/specgen/validate.go new file mode 100644 index 000000000..78e4d8ad5 --- /dev/null +++ b/pkg/specgen/validate.go @@ -0,0 +1,159 @@ +package specgen + +import ( + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +var ( + // ErrInvalidSpecConfig describes an error that the given SpecGenerator is invalid + ErrInvalidSpecConfig error = errors.New("invalid configuration") + // SystemDValues describes the only values that SystemD can be + SystemDValues = []string{"true", "false", "always"} + // ImageVolumeModeValues describes the only values that ImageVolumeMode can be + ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"} +) + +func exclusiveOptions(opt1, opt2 string) error { + return errors.Errorf("%s and %s are mutually exclusive options", opt1, opt2) +} + +// Validate verifies that the given SpecGenerator is valid and satisfies required +// input for creating a container. +func (s *SpecGenerator) validate(rt *libpod.Runtime) error { + + // + // ContainerBasicConfig + // + // Rootfs and Image cannot both populated + if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { + return errors.Wrap(ErrInvalidSpecConfig, "both image and rootfs cannot be simultaneously") + } + // Cannot set hostname and utsns + if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() { + return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace") + } + // systemd values must be true, false, or always + if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) { + return errors.Wrapf(ErrInvalidSpecConfig, "SystemD values must be one of %s", strings.Join(SystemDValues, ",")) + } + + // + // ContainerStorageConfig + // + // rootfs and image cannot both be set + if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { + return exclusiveOptions("rootfs", "image") + } + // imagevolumemode must be one of ignore, tmpfs, or anonymous if given + if len(s.ContainerStorageConfig.ImageVolumeMode) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerStorageConfig.ImageVolumeMode), ImageVolumeModeValues) { + return errors.Errorf("ImageVolumeMode values must be one of %s", strings.Join(ImageVolumeModeValues, ",")) + } + // shmsize conflicts with IPC namespace + if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() { + return errors.New("cannot set shmsize when creating an IPC namespace") + } + + // + // ContainerSecurityConfig + // + // groups and privileged are exclusive + if len(s.Groups) > 0 && s.Privileged { + return exclusiveOptions("Groups", "privileged") + } + // capadd and privileged are exclusive + if len(s.CapAdd) > 0 && s.Privileged { + return exclusiveOptions("CapAdd", "privileged") + } + // selinuxprocesslabel and privileged are exclusive + if len(s.SelinuxProcessLabel) > 0 && s.Privileged { + return exclusiveOptions("SelinuxProcessLabel", "privileged") + } + // selinuxmounmtlabel and privileged are exclusive + if len(s.SelinuxMountLabel) > 0 && s.Privileged { + return exclusiveOptions("SelinuxMountLabel", "privileged") + } + // selinuxopts and privileged are exclusive + if len(s.SelinuxOpts) > 0 && s.Privileged { + return exclusiveOptions("SelinuxOpts", "privileged") + } + // apparmor and privileged are exclusive + if len(s.ApparmorProfile) > 0 && s.Privileged { + return exclusiveOptions("AppArmorProfile", "privileged") + } + // userns and idmappings conflict + if s.UserNS.IsPrivate() && s.IDMappings == nil { + return errors.Wrap(ErrInvalidSpecConfig, "IDMappings are required when not creating a User namespace") + } + + // + // ContainerCgroupConfig + // + // + // None for now + + // + // ContainerNetworkConfig + // + if !s.NetNS.IsPrivate() && s.ConfigureNetNS { + return errors.New("can only configure network namespace when creating a network a network namespace") + } + // useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption + if s.UseImageResolvConf { + if len(s.DNSServer) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSServer") + } + if len(s.DNSSearch) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSSearch") + } + if len(s.DNSOption) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSOption") + } + } + // UseImageHosts and HostAdd are exclusive + if s.UseImageHosts && len(s.HostAdd) > 0 { + return exclusiveOptions("UseImageHosts", "HostAdd") + } + + // TODO the specgen does not appear to handle this? Should it + //switch config.Cgroup.Cgroups { + //case "disabled": + // if addedResources { + // return errors.New("cannot specify resource limits when cgroups are disabled is specified") + // } + // configSpec.Linux.Resources = &spec.LinuxResources{} + //case "enabled", "no-conmon", "": + // // Do nothing + //default: + // return errors.New("unrecognized option for cgroups; supported are 'default', 'disabled', 'no-conmon'") + //} + + // Namespaces + if err := s.UtsNS.validate(); err != nil { + return err + } + if err := s.IpcNS.validate(); err != nil { + return err + } + if err := s.NetNS.validate(); err != nil { + return err + } + if err := s.PidNS.validate(); err != nil { + return err + } + if err := s.CgroupNS.validate(); err != nil { + return err + } + if err := s.UserNS.validate(); err != nil { + return err + } + + // The following are defaults as needed by container creation + if len(s.WorkDir) < 1 { + s.WorkDir = "/" + } + return nil +} diff --git a/pkg/systemd/activation.go b/pkg/systemd/activation.go new file mode 100644 index 000000000..c8b2389dc --- /dev/null +++ b/pkg/systemd/activation.go @@ -0,0 +1,40 @@ +package systemd + +import ( + "os" + "strconv" + "strings" +) + +// SocketActivated determine if podman is running under the socket activation protocol +func SocketActivated() bool { + pid, pid_found := os.LookupEnv("LISTEN_PID") + fds, fds_found := os.LookupEnv("LISTEN_FDS") + fdnames, fdnames_found := os.LookupEnv("LISTEN_FDNAMES") + + if !(pid_found && fds_found && fdnames_found) { + return false + } + + p, err := strconv.Atoi(pid) + if err != nil || p != os.Getpid() { + return false + } + + nfds, err := strconv.Atoi(fds) + if err != nil || nfds < 1 { + return false + } + + // First available file descriptor is always 3. + if nfds > 1 { + names := strings.Split(fdnames, ":") + for _, n := range names { + if strings.Contains(n, "podman") { + return true + } + } + } + + return true +} diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemd/generate/systemdgen.go index 26b3b3756..404347828 100644 --- a/pkg/systemdgen/systemdgen.go +++ b/pkg/systemd/generate/systemdgen.go @@ -1,4 +1,4 @@ -package systemdgen +package generate import ( "bytes" diff --git a/pkg/systemdgen/systemdgen_test.go b/pkg/systemd/generate/systemdgen_test.go index ee2429407..b74b75258 100644 --- a/pkg/systemdgen/systemdgen_test.go +++ b/pkg/systemd/generate/systemdgen_test.go @@ -1,4 +1,4 @@ -package systemdgen +package generate import ( "testing" |