diff options
Diffstat (limited to 'pkg')
55 files changed, 751 insertions, 519 deletions
diff --git a/pkg/api/Makefile b/pkg/api/Makefile index 6b24bfd83..6da5fb57e 100644 --- a/pkg/api/Makefile +++ b/pkg/api/Makefile @@ -5,6 +5,9 @@ SWAGGER_OUT ?= swagger.yaml validate: ${SWAGGER_OUT} swagger validate ${SWAGGER_OUT} +serve: ${SWAGGER_OUT} + swagger serve -F redoc -p=8080 swagger.yaml + .PHONY: ${SWAGGER_OUT} ${SWAGGER_OUT}: # generate doesn't remove file on error diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go index 391aa752d..f1ed1b2b8 100644 --- a/pkg/api/handlers/compat/containers_start.go +++ b/pkg/api/handlers/compat/containers_start.go @@ -42,7 +42,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNotModified, nil) return } - if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil { + if err := con.Start(r.Context(), true); err != nil { utils.InternalServerError(w, err) return } diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index ab92434b1..ec40fdd2d 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -15,6 +15,7 @@ import ( "github.com/containers/buildah" buildahDefine "github.com/containers/buildah/define" + "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" @@ -445,17 +446,34 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { }, } + if len(query.Platform) > 0 { + variant := "" + buildOptions.OS, buildOptions.Architecture, variant, err = parse.Platform(query.Platform) + if err != nil { + utils.BadRequest(w, "platform", query.Platform, err) + return + } + buildOptions.SystemContext.OSChoice = buildOptions.OS + buildOptions.SystemContext.ArchitectureChoice = buildOptions.Architecture + buildOptions.SystemContext.VariantChoice = variant + } if _, found := r.URL.Query()["timestamp"]; found { ts := time.Unix(query.Timestamp, 0) buildOptions.Timestamp = &ts } + var ( + imageID string + success bool + ) + runCtx, cancel := context.WithCancel(context.Background()) - var imageID string go func() { defer cancel() imageID, _, err = runtime.Build(r.Context(), buildOptions, query.Dockerfile) - if err != nil { + if err == nil { + success = true + } else { stderr.Write([]byte(err.Error() + "\n")) } }() @@ -471,8 +489,6 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") flush() - var failed bool - body := w.(io.Writer) if logrus.IsLevelEnabled(logrus.DebugLevel) { if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found { @@ -513,14 +529,14 @@ loop: } flush() case e := <-stderr.Chan(): - failed = true m.Error = string(e) if err := enc.Encode(m); err != nil { logrus.Warnf("Failed to json encode error %v", err) } flush() case <-runCtx.Done(): - if !failed { + flush() + if success { if !utils.IsLibpodRequest(r) { m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID) if err := enc.Encode(m); err != nil { diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go index 42ece643b..d86fc1e19 100644 --- a/pkg/api/handlers/compat/volumes.go +++ b/pkg/api/handlers/compat/volumes.go @@ -266,7 +266,7 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { } f := (url.Values)(*filterMap) - filterFuncs, err := filters.GenerateVolumeFilters(f) + filterFuncs, err := filters.GeneratePruneVolumeFilters(f) if err != nil { utils.Error(w, "Something when wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to parse filters for %s", f.Encode())) return diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 158babcdc..92882cc40 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -270,6 +270,16 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { return } + // if format is dir, server will save to an archive + // the client will unArchive after receive the archive file + // so must convert is at here + switch query.Format { + case define.OCIManifestDir: + query.Format = define.OCIArchive + case define.V2s2ManifestDir: + query.Format = define.V2s2Archive + } + switch query.Format { case define.V2s2Archive, define.OCIArchive: tmpfile, err := ioutil.TempFile("", "api.tar") diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 5ababc36b..6a491ae48 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" + "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" @@ -34,6 +35,16 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } + + // TODO: (jhonce) When c/image is refactored the roadmap calls for this check to be pushed into that library. + for _, n := range query.Name { + if _, err := reference.ParseNormalizedNamed(n); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "invalid image name %s", n)) + return + } + } + rtc, err := runtime.GetConfig() if err != nil { utils.InternalServerError(w, err) @@ -75,11 +86,16 @@ func ManifestInspect(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusNotFound, inspectError) return } + var list manifest.Schema2List if err := json.Unmarshal(inspectReport, &list); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Unmarshal()")) return } + if list.Manifests == nil { + list.Manifests = make([]manifest.Schema2ManifestDescriptor, 0) + } + utils.WriteResponse(w, http.StatusOK, &list) } diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index eba5386b6..96f572a8b 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -3,6 +3,7 @@ package libpod import ( "io" "io/ioutil" + "net" "net/http" "os" @@ -20,10 +21,11 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Network string `schema:"network"` - TLSVerify bool `schema:"tlsVerify"` - LogDriver string `schema:"logDriver"` - Start bool `schema:"start"` + Network string `schema:"network"` + TLSVerify bool `schema:"tlsVerify"` + LogDriver string `schema:"logDriver"` + Start bool `schema:"start"` + StaticIPs []string `schema:"staticIPs"` }{ TLSVerify: true, Start: true, @@ -35,6 +37,17 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { return } + staticIPs := make([]net.IP, 0, len(query.StaticIPs)) + for _, ipString := range query.StaticIPs { + ip := net.ParseIP(ipString) + if ip == nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Errorf("Invalid IP address %s", ipString)) + return + } + staticIPs = append(staticIPs, ip) + } + // Fetch the K8s YAML file from the body, and copy it to a temp file. tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml") if err != nil { @@ -71,6 +84,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { Network: query.Network, Quiet: true, LogDriver: query.LogDriver, + StaticIPs: staticIPs, } if _, found := r.URL.Query()["tlsVerify"]; found { options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index 2631f19ac..9450a70d9 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -25,7 +25,7 @@ type swagInspectPodResponse struct { // swagger:response InspectManifest type swagInspectManifestResponse struct { // in:body - Body manifest.List + Body manifest.Schema2List } // Kill Pod diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go index 02457eb8f..2b4cef1bb 100644 --- a/pkg/api/handlers/libpod/system.go +++ b/pkg/api/handlers/libpod/system.go @@ -72,6 +72,7 @@ func DiskUsage(w http.ResponseWriter, r *http.Request) { response, err := ic.SystemDf(r.Context(), options) if err != nil { utils.InternalServerError(w, err) + return } utils.WriteResponse(w, http.StatusOK, response) } diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index 442b53d1e..68aec30d5 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -150,7 +150,7 @@ func pruneVolumesHelper(r *http.Request) ([]*reports.PruneReport, error) { } f := (url.Values)(*filterMap) - filterFuncs, err := filters.GenerateVolumeFilters(f) + filterFuncs, err := filters.GeneratePruneVolumeFilters(f) if err != nil { return nil, err } diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 3bf3e4e11..536c4707a 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -774,7 +774,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/prune"), s.APIHandler(compat.PruneContainers)).Methods(http.MethodPost) - // swagger:operation GET /libpod/containers/showmounted libpod ShowMountedContainersLibpod + // swagger:operation GET /libpod/containers/showmounted libpod ContainerShowMountedLibpod // --- // tags: // - containers @@ -1194,11 +1194,22 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // - removing // - stopping // description: "Conditions to wait for. If no condition provided the 'exited' condition is assumed." + // - in: query + // name: interval + // type: string + // default: "250ms" + // description: Time Interval to wait before polling for completion. // produces: // - application/json + // - text/plain // responses: // 200: - // $ref: "#/responses/ContainerWaitResponse" + // description: Status code + // schema: + // type: integer + // format: int32 + // examples: + // text/plain: 137 // 404: // $ref: "#/responses/NoSuchContainer" // 500: @@ -1468,8 +1479,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/restore"), s.APIHandler(libpod.Restore)).Methods(http.MethodPost) - // swagger:operation GET /containers/{name}/changes libpod ContainerChangesLibpod - // swagger:operation GET /libpod/containers/{name}/changes compat ContainerChanges + // swagger:operation GET /containers/{name}/changes compat ContainerChanges + // swagger:operation GET /libpod/containers/{name}/changes libpod ContainerChangesLibpod // --- // tags: // - containers diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go index 208bf668c..e10c7029c 100644 --- a/pkg/api/server/register_generate.go +++ b/pkg/api/server/register_generate.go @@ -8,7 +8,7 @@ import ( ) func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { - // swagger:operation GET /libpod/generate/{name:.*}/systemd libpod GenerateSystemdLibpod + // swagger:operation GET /libpod/generate/{name}/systemd libpod GenerateSystemdLibpod // --- // tags: // - containers @@ -17,7 +17,7 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { // description: Generate Systemd Units based on a pod or container. // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: Name or ID of the container or pod. diff --git a/pkg/api/server/register_healthcheck.go b/pkg/api/server/register_healthcheck.go index e2fd5ac68..7d8541a51 100644 --- a/pkg/api/server/register_healthcheck.go +++ b/pkg/api/server/register_healthcheck.go @@ -8,7 +8,7 @@ import ( ) func (s *APIServer) registerHealthCheckHandlers(r *mux.Router) error { - // swagger:operation GET /libpod/containers/{name:.*}/healthcheck libpod ContainerHealthcheckLibpod + // swagger:operation GET /libpod/containers/{name}/healthcheck libpod ContainerHealthcheckLibpod // --- // tags: // - containers @@ -16,7 +16,7 @@ func (s *APIServer) registerHealthCheckHandlers(r *mux.Router) error { // description: Execute the defined healthcheck and return information about the results // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: the name or ID of the container diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index de8d50cc9..b32c0df20 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -188,7 +188,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/search"), s.APIHandler(compat.SearchImages)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths r.Handle("/images/search", s.APIHandler(compat.SearchImages)).Methods(http.MethodGet) - // swagger:operation DELETE /images/{name:.*} compat ImageDelete + // swagger:operation DELETE /images/{name} compat ImageDelete // --- // tags: // - images (compat) @@ -196,7 +196,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 @@ -222,7 +222,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/{name:.*}"), s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete) // Added non version path to URI to support docker non versioned paths r.Handle("/images/{name:.*}", s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete) - // swagger:operation POST /images/{name:.*}/push compat ImagePush + // swagger:operation POST /images/{name}/push compat ImagePush // --- // tags: // - images (compat) @@ -230,7 +230,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Push an image to a container registry // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: Name of image to push. @@ -269,7 +269,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/{name:.*}/push"), s.APIHandler(compat.PushImage)).Methods(http.MethodPost) // Added non version path to URI to support docker non versioned paths r.Handle("/images/{name:.*}/push", s.APIHandler(compat.PushImage)).Methods(http.MethodPost) - // swagger:operation GET /images/{name:.*}/get compat ImageGet + // swagger:operation GET /images/{name}/get compat ImageGet // --- // tags: // - images (compat) @@ -277,7 +277,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 @@ -319,7 +319,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/get"), s.APIHandler(compat.ExportImages)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths r.Handle("/images/get", s.APIHandler(compat.ExportImages)).Methods(http.MethodGet) - // swagger:operation GET /images/{name:.*}/history compat ImageHistory + // swagger:operation GET /images/{name}/history compat ImageHistory // --- // tags: // - images (compat) @@ -327,7 +327,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 @@ -343,7 +343,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/{name:.*}/history"), s.APIHandler(compat.HistoryImage)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths r.Handle("/images/{name:.*}/history", s.APIHandler(compat.HistoryImage)).Methods(http.MethodGet) - // swagger:operation GET /images/{name:.*}/json compat ImageInspect + // swagger:operation GET /images/{name}/json compat ImageInspect // --- // tags: // - images (compat) @@ -351,7 +351,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 @@ -367,7 +367,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/{name:.*}/json"), s.APIHandler(compat.GetImage)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths r.Handle("/images/{name:.*}/json", s.APIHandler(compat.GetImage)).Methods(http.MethodGet) - // swagger:operation POST /images/{name:.*}/tag compat ImageTag + // swagger:operation POST /images/{name}/tag compat ImageTag // --- // tags: // - images (compat) @@ -375,7 +375,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 @@ -667,7 +667,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { libpod endpoints */ - // swagger:operation POST /libpod/images/{name:.*}/push libpod ImagePushLibpod + // swagger:operation POST /libpod/images/{name}/push libpod ImagePushLibpod // --- // tags: // - images @@ -675,7 +675,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Push an image to a container registry // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: Name of image to push. @@ -705,7 +705,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/{name:.*}/push"), s.APIHandler(libpod.PushImage)).Methods(http.MethodPost) - // swagger:operation GET /libpod/images/{name:.*}/exists libpod ImageExistsLibpod + // swagger:operation GET /libpod/images/{name}/exists libpod ImageExistsLibpod // --- // tags: // - images @@ -713,7 +713,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 @@ -727,7 +727,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/{name:.*}/exists"), s.APIHandler(libpod.ImageExists)).Methods(http.MethodGet) - // swagger:operation GET /libpod/images/{name:.*}/tree libpod ImageTreeLibpod + // swagger:operation GET /libpod/images/{name}/tree libpod ImageTreeLibpod // --- // tags: // - images @@ -735,7 +735,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 @@ -753,7 +753,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/{name:.*}/tree"), s.APIHandler(libpod.ImageTree)).Methods(http.MethodGet) - // swagger:operation GET /libpod/images/{name:.*}/history libpod ImageHistoryLibpod + // swagger:operation GET /libpod/images/{name}/history libpod ImageHistoryLibpod // --- // tags: // - images @@ -761,7 +761,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 @@ -906,7 +906,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesBatchRemove)).Methods(http.MethodDelete) - // swagger:operation DELETE /libpod/images/{name:.*} libpod ImageDeleteLibpod + // swagger:operation DELETE /libpod/images/{name} libpod ImageDeleteLibpod // --- // tags: // - images @@ -914,7 +914,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Remove an image from the local storage. // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: name or ID of image to remove @@ -1042,7 +1042,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(compat.SearchImages)).Methods(http.MethodGet) - // swagger:operation GET /libpod/images/{name:.*}/get libpod ImageGetLibpod + // swagger:operation GET /libpod/images/{name}/get libpod ImageGetLibpod // --- // tags: // - images @@ -1050,7 +1050,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Export an image // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: the name or ID of the container @@ -1109,7 +1109,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/export"), s.APIHandler(libpod.ExportImages)).Methods(http.MethodGet) - // swagger:operation GET /libpod/images/{name:.*}/json libpod ImageInspectLibpod + // swagger:operation GET /libpod/images/{name}/json libpod ImageInspectLibpod // --- // tags: // - images @@ -1117,7 +1117,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 @@ -1131,7 +1131,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/{name:.*}/json"), s.APIHandler(libpod.GetImage)).Methods(http.MethodGet) - // swagger:operation POST /libpod/images/{name:.*}/tag libpod ImageTagLibpod + // swagger:operation POST /libpod/images/{name}/tag libpod ImageTagLibpod // --- // tags: // - images @@ -1139,7 +1139,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 @@ -1217,7 +1217,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/commit"), s.APIHandler(libpod.CommitContainer)).Methods(http.MethodPost) - // swagger:operation POST /libpod/images/{name:.*}/untag libpod ImageUntagLibpod + // swagger:operation POST /libpod/images/{name}/untag libpod ImageUntagLibpod // --- // tags: // - images @@ -1225,7 +1225,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Untag an image. If not repo and tag are specified, all tags are removed from the image. // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: the name or ID of the container diff --git a/pkg/api/server/register_manifest.go b/pkg/api/server/register_manifest.go index 0417462a6..010d8a79e 100644 --- a/pkg/api/server/register_manifest.go +++ b/pkg/api/server/register_manifest.go @@ -59,7 +59,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/manifests/{name}/exists"), s.APIHandler(libpod.ExistsManifest)).Methods(http.MethodGet) - // swagger:operation GET /libpod/manifests/{name:.*}/json manifests ManifestInspectLibpod + // swagger:operation GET /libpod/manifests/{name}/json manifests ManifestInspectLibpod // --- // summary: Inspect // description: Display a manifest list @@ -67,7 +67,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // - application/json // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: the name or ID of the manifest @@ -79,14 +79,15 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/{name:.*}/json"), s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet) - // swagger:operation POST /libpod/manifests/{name:.*}/add manifests ManifestAddLibpod + // swagger:operation POST /libpod/manifests/{name}/add manifests ManifestAddLibpod // --- + // summary: Add image // description: Add an image to a manifest list // produces: // - application/json // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: the name or ID of the manifest @@ -106,7 +107,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/{name:.*}/add"), s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost) - // swagger:operation DELETE /libpod/manifests/{name:.*} manifests ManifestDeleteLibpod + // swagger:operation DELETE /libpod/manifests/{name} manifests ManifestDeleteLibpod // --- // summary: Remove // description: Remove an image from a manifest list @@ -114,7 +115,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // - application/json // parameters: // - in: path - // name: name:.* + // name: name // type: string // required: true // description: the image associated with the manifest diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index e25fd071c..dcec61bef 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -377,7 +377,6 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // name: filters // type: string // description: | - // NOT IMPLEMENTED // Filters to process on the prune list, encoded as JSON (a map[string][]string). // Available filters: // - until=<timestamp> Prune networks created before this timestamp. The <timestamp> can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the daemon machine’s time. diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go index b9d3349be..da37abb70 100644 --- a/pkg/api/server/register_play.go +++ b/pkg/api/server/register_play.go @@ -8,7 +8,7 @@ import ( ) func (s *APIServer) registerPlayHandlers(r *mux.Router) error { - // swagger:operation POST /libpod/play/kube libpod KubePlayLibpod + // swagger:operation POST /libpod/play/kube libpod PlayKubeLibpod // --- // tags: // - containers @@ -34,6 +34,12 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error { // type: boolean // default: true // description: Start the pod after creating it. + // - in: query + // name: staticIPs + // type: array + // description: Static IPs used for the pods. + // items: + // type: string // - in: body // name: request // description: Kubernetes YAML file. diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index 226cbce11..3bcc50ba4 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -38,7 +38,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // schema: // $ref: "#/definitions/PodSpecGenerator" // responses: - // 200: + // 201: // schema: // $ref: "#/definitions/IdResponse" // 400: @@ -305,7 +305,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name}/top"), s.APIHandler(libpod.PodTop)).Methods(http.MethodGet) - // swagger:operation GET /libpod/pods/stats pods PodStatsLibpod + // swagger:operation GET /libpod/pods/stats pods PodStatsAllLibpod // --- // tags: // - pods diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go index 58f056626..e5d6cf195 100644 --- a/pkg/api/server/register_volumes.go +++ b/pkg/api/server/register_volumes.go @@ -81,6 +81,14 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // summary: Prune volumes // produces: // - application/json + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // JSON encoded value of filters (a map[string][]string) to match volumes against before pruning. + // Available filters: + // - label (label=<key>, label=<key>=<value>, label!=<key>, or label!=<key>=<value>) Prune volumes with (or without, in case label!=... is used) the specified labels. // responses: // '200': // "$ref": "#/responses/VolumePruneResponse" @@ -259,8 +267,8 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // type: string // description: | // JSON encoded value of filters (a map[string][]string) to match volumes against before pruning. - // - // Note: No filters are currently supported and any filters specified will cause an error response. + // Available filters: + // - label (label=<key>, label=<key>=<value>, label!=<key>, or label!=<key>=<value>) Prune volumes with (or without, in case label!=... is used) the specified labels. // responses: // '200': // "$ref": "#/responses/DockerVolumePruneResponse" diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index f63e35bf1..0d22c32f8 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -154,6 +154,7 @@ type RestartOptions struct { // StartOptions are optional options for starting containers type StartOptions struct { DetachKeys *string + Recursive *bool } //go:generate go run ../generator/generator.go StatsOptions diff --git a/pkg/bindings/containers/types_start_options.go b/pkg/bindings/containers/types_start_options.go index f8ba29623..d419c755c 100644 --- a/pkg/bindings/containers/types_start_options.go +++ b/pkg/bindings/containers/types_start_options.go @@ -35,3 +35,19 @@ func (o *StartOptions) GetDetachKeys() string { } return *o.DetachKeys } + +// WithRecursive +func (o *StartOptions) WithRecursive(value bool) *StartOptions { + v := &value + o.Recursive = v + return o +} + +// GetRecursive +func (o *StartOptions) GetRecursive() bool { + var recursive bool + if o.Recursive == nil { + return recursive + } + return *o.Recursive +} diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index c47a16551..c0e5706a5 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "strconv" "strings" @@ -190,6 +191,10 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO platform = "linux" } platform += "/" + options.Architecture + } else { + if len(platform) > 0 { + platform += "/" + runtime.GOARCH + } } if len(platform) > 0 { params.Set("platform", platform) @@ -335,6 +340,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO re := regexp.MustCompile(`[0-9a-f]{12}`) var id string + var mErr error for { var s struct { Stream string `json:"stream,omitempty"` @@ -342,11 +348,21 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } if err := dec.Decode(&s); err != nil { if errors.Is(err, io.EOF) { - return &entities.BuildReport{ID: id}, nil + if mErr == nil && id == "" { + mErr = errors.New("stream dropped, unexpected failure") + } + break } s.Error = err.Error() + "\n" } + select { + case <-response.Request.Context().Done(): + return &entities.BuildReport{ID: id}, mErr + default: + // non-blocking select + } + switch { case s.Stream != "": stdout.Write([]byte(s.Stream)) @@ -354,11 +370,12 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO id = strings.TrimSuffix(s.Stream, "\n") } case s.Error != "": - return nil, errors.New(s.Error) + mErr = errors.New(s.Error) default: return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input") } } + return &entities.BuildReport{ID: id}, mErr } func nTar(excludes []string, sources ...string) (io.ReadCloser, error) { diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 6f3aa8594..17451c273 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -184,7 +184,13 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, // Prune removes unused CNI networks func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPruneReport, error) { - // TODO Filters is not implemented + if options == nil { + options = new(PruneOptions) + } + params, err := options.ToParams() + if err != nil { + return nil, err + } var ( prunedNetworks []*entities.NetworkPruneReport ) @@ -193,7 +199,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPrune return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", nil, nil) + response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", params, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/network/types.go b/pkg/bindings/network/types.go index 47dce67c7..e62ae8f52 100644 --- a/pkg/bindings/network/types.go +++ b/pkg/bindings/network/types.go @@ -79,4 +79,7 @@ type ExistsOptions struct { // PruneOptions are optional options for removing unused // CNI networks type PruneOptions struct { + // Filters are applied to the prune of networks to be more + // specific on choosing + Filters map[string][]string } diff --git a/pkg/bindings/network/types_prune_options.go b/pkg/bindings/network/types_prune_options.go index 84e1a8b32..f17e09d69 100644 --- a/pkg/bindings/network/types_prune_options.go +++ b/pkg/bindings/network/types_prune_options.go @@ -19,3 +19,19 @@ func (o *PruneOptions) Changed(fieldName string) bool { func (o *PruneOptions) ToParams() (url.Values, error) { return util.ToParams(o) } + +// WithFilters +func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions { + v := value + o.Filters = v + return o +} + +// GetFilters +func (o *PruneOptions) GetFilters() map[string][]string { + var filters map[string][]string + if o.Filters == nil { + return filters + } + return o.Filters +} diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go index 5fb9a4d41..6598ec3c2 100644 --- a/pkg/bindings/play/types.go +++ b/pkg/bindings/play/types.go @@ -1,5 +1,7 @@ package play +import "net" + //go:generate go run ../generator/generator.go KubeOptions // KubeOptions are optional options for replaying kube YAML files type KubeOptions struct { @@ -23,6 +25,8 @@ type KubeOptions struct { // SeccompProfileRoot - path to a directory containing seccomp // profiles. SeccompProfileRoot *string + // StaticIPs - Static IP address used by the pod(s). + StaticIPs *[]net.IP // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps *[]string // LogDriver for the container. For example: journald diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/play/types_kube_options.go index 78396a090..a1786f553 100644 --- a/pkg/bindings/play/types_kube_options.go +++ b/pkg/bindings/play/types_kube_options.go @@ -1,6 +1,7 @@ package play import ( + "net" "net/url" "github.com/containers/podman/v3/pkg/bindings/internal/util" @@ -164,6 +165,22 @@ func (o *KubeOptions) GetSeccompProfileRoot() string { return *o.SeccompProfileRoot } +// WithStaticIPs +func (o *KubeOptions) WithStaticIPs(value []net.IP) *KubeOptions { + v := &value + o.StaticIPs = v + return o +} + +// GetStaticIPs +func (o *KubeOptions) GetStaticIPs() []net.IP { + var staticIPs []net.IP + if o.StaticIPs == nil { + return staticIPs + } + return *o.StaticIPs +} + // WithConfigMaps func (o *KubeOptions) WithConfigMaps(value []string) *KubeOptions { v := &value diff --git a/pkg/bindings/test/networks_test.go b/pkg/bindings/test/networks_test.go index ef20235ae..b53fc4bd3 100644 --- a/pkg/bindings/test/networks_test.go +++ b/pkg/bindings/test/networks_test.go @@ -2,10 +2,12 @@ package test_bindings import ( "context" + "fmt" "net/http" "time" "github.com/containers/podman/v3/pkg/bindings" + "github.com/containers/podman/v3/pkg/bindings/containers" "github.com/containers/podman/v3/pkg/bindings/network" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -37,6 +39,53 @@ var _ = Describe("Podman networks", func() { bt.cleanup() }) + It("podman prune unused networks with filters", func() { + name := "foobar" + opts := network.CreateOptions{ + Name: &name, + } + _, err = network.Create(connText, &opts) + Expect(err).To(BeNil()) + + // Invalid filters should return error + filtersIncorrect := map[string][]string{ + "status": {"dummy"}, + } + _, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filtersIncorrect)) + Expect(err).ToNot(BeNil()) + + // List filter params should not work with prune. + filtersIncorrect = map[string][]string{ + "name": {name}, + } + _, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filtersIncorrect)) + Expect(err).ToNot(BeNil()) + + // Mismatched label, correct filter params => no network should be pruned. + filtersIncorrect = map[string][]string{ + "label": {"xyz"}, + } + pruneResponse, err := network.Prune(connText, new(network.PruneOptions).WithFilters(filtersIncorrect)) + Expect(err).To(BeNil()) + Expect(len(pruneResponse)).To(Equal(0)) + + // Mismatched until, correct filter params => no network should be pruned. + filters := map[string][]string{ + "until": {"50"}, // January 1, 1970 + } + pruneResponse, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filters)) + Expect(err).To(BeNil()) + Expect(len(pruneResponse)).To(Equal(0)) + + // Valid filter params => network should be pruned now. + filters = map[string][]string{ + "until": {"5000000000"}, //June 11, 2128 + } + pruneResponse, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filters)) + Expect(err).To(BeNil()) + Expect(len(pruneResponse)).To(Equal(1)) + }) + It("create network", func() { // create a network with blank config should work _, err = network.Create(connText, &network.CreateOptions{}) @@ -69,4 +118,88 @@ var _ = Describe("Podman networks", func() { Expect(err).To(BeNil()) Expect(data[0]["name"]).To(Equal(name)) }) + + It("list networks", func() { + // create a bunch of named networks and make verify with list + netNames := []string{"homer", "bart", "lisa", "maggie", "marge"} + for i := 0; i < 5; i++ { + opts := network.CreateOptions{ + Name: &netNames[i], + } + _, err = network.Create(connText, &opts) + Expect(err).To(BeNil()) + } + list, err := network.List(connText, nil) + Expect(err).To(BeNil()) + Expect(len(list)).To(BeNumerically(">=", 5)) + for _, n := range list { + if n.Name != "podman" { + Expect(StringInSlice(n.Name, netNames)).To(BeTrue()) + } + } + + // list with bad filter should be 500 + filters := make(map[string][]string) + filters["foobar"] = []string{"1234"} + options := new(network.ListOptions).WithFilters(filters) + _, err = network.List(connText, options) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + // filter list with success + filters = make(map[string][]string) + filters["name"] = []string{"homer"} + options = new(network.ListOptions).WithFilters(filters) + list, err = network.List(connText, options) + Expect(err).To(BeNil()) + Expect(len(list)).To(BeNumerically("==", 1)) + Expect(list[0].Name).To(Equal("homer")) + }) + + It("remove network", func() { + // removing a noName network should result in 404 + _, err := network.Remove(connText, "noName", nil) + code, err := bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + // Removing an unused network should work + name := "unused" + opts := network.CreateOptions{ + Name: &name, + } + _, err = network.Create(connText, &opts) + Expect(err).To(BeNil()) + report, err := network.Remove(connText, name, nil) + Expect(err).To(BeNil()) + Expect(report[0].Name).To(Equal(name)) + + // Removing a network that is being used without force should be 500 + name = "used" + opts = network.CreateOptions{ + Name: &name, + } + _, err = network.Create(connText, &opts) + Expect(err).To(BeNil()) + + // Start container and wait + container := "ntest" + session := bt.runPodman([]string{"run", "-dt", fmt.Sprintf("--network=%s", name), "--name", container, alpine.name, "top"}) + session.Wait(45) + Expect(session.ExitCode()).To(BeZero()) + + _, err = network.Remove(connText, name, nil) + code, err = bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + // Removing with a network in use with force should work with a stopped container + err = containers.Stop(connText, container, new(containers.StopOptions).WithTimeout(0)) + Expect(err).To(BeNil()) + options := new(network.RemoveOptions).WithForce(true) + report, err = network.Remove(connText, name, options) + Expect(err).To(BeNil()) + Expect(report[0].Name).To(Equal(name)) + }) }) diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go index 91f6444cc..14bda114e 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -83,7 +83,8 @@ var _ = Describe("Podman volumes", func() { It("remove volume", func() { // removing a bogus volume should result in 404 err := volumes.Remove(connText, "foobar", nil) - code, _ := bindings.CheckResponseCode(err) + code, err := bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Removing an unused volume should work @@ -97,9 +98,12 @@ var _ = Describe("Podman volumes", func() { Expect(err).To(BeNil()) session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/foobar", vol.Name), "--name", "vtest", alpine.name, "top"}) session.Wait(45) + Expect(session.ExitCode()).To(BeZero()) + err = volumes.Remove(connText, vol.Name, nil) Expect(err).ToNot(BeNil()) - code, _ = bindings.CheckResponseCode(err) + code, err = bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) Expect(code).To(BeNumerically("==", http.StatusConflict)) // Removing with a volume in use with force should work with a stopped container diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 608e1647a..aefb5183b 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -265,7 +265,7 @@ func readFileAsUint64(path string) (uint64, error) { if v == "max" { return math.MaxUint64, nil } - ret, err := strconv.ParseUint(v, 10, 0) + ret, err := strconv.ParseUint(v, 10, 64) if err != nil { return ret, errors.Wrapf(err, "parse %s from %s", v, path) } diff --git a/pkg/cgroups/cpu.go b/pkg/cgroups/cpu.go index 05223c2e1..23539757d 100644 --- a/pkg/cgroups/cpu.go +++ b/pkg/cgroups/cpu.go @@ -40,7 +40,7 @@ func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) { if s == "" { break } - v, err := strconv.ParseUint(s, 10, 0) + v, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, errors.Wrapf(err, "parsing %s", s) } @@ -80,14 +80,14 @@ func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error { return err } if val, found := values["usage_usec"]; found { - usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 0) + usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 64) if err != nil { return err } usage.Kernel *= 1000 } if val, found := values["system_usec"]; found { - usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 0) + usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 64) if err != nil { return err } @@ -149,7 +149,7 @@ func GetSystemCPUUsage() (uint64, error) { } if val, found := values["usage_usec"]; found { - v, err := strconv.ParseUint(cleanString(val[0]), 10, 0) + v, err := strconv.ParseUint(cleanString(val[0]), 10, 64) if err != nil { return 0, err } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index bcab617af..f695d32fd 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -88,7 +88,7 @@ type ContainerEngine interface { SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error) Shutdown(ctx context.Context) SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error) - Unshare(ctx context.Context, args []string) error + Unshare(ctx context.Context, args []string, options SystemUnshareOptions) error Version(ctx context.Context) (*SystemVersionReport, error) VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error) diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index 6883fe6c5..c69bb0867 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -1,6 +1,10 @@ package entities -import "github.com/containers/image/v5/types" +import ( + "net" + + "github.com/containers/image/v5/types" +) // PlayKubeOptions controls playing kube YAML files. type PlayKubeOptions struct { @@ -24,6 +28,8 @@ type PlayKubeOptions struct { // SeccompProfileRoot - path to a directory containing seccomp // profiles. SeccompProfileRoot string + // StaticIPs - Static IP address used by the pod(s). + StaticIPs []net.IP // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps []string // LogDriver for the container. For example: journald @@ -45,8 +51,16 @@ type PlayKubePod struct { ContainerErrors []string } +// PlayKubeVolume represents a single volume created by play kube. +type PlayKubeVolume struct { + // Name - Name of the volume created by play kube. + Name string +} + // PlayKubeReport contains the results of running play kube. type PlayKubeReport struct { // Pods - pods created by play kube. Pods []PlayKubePod + // Volumes - volumes created by play kube. + Volumes []PlayKubeVolume } diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index 1a671d59e..31a6185dc 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -98,6 +98,11 @@ type SystemVersionReport struct { Server *define.Version `json:",omitempty"` } +// SystemUnshareOptions describes the options for the unshare command +type SystemUnshareOptions struct { + RootlessCNI bool +} + type ComponentVersion struct { types.Version } diff --git a/pkg/domain/entities/types/auth.go b/pkg/domain/entities/types/auth.go index ddf15bb18..7f2480173 100644 --- a/pkg/domain/entities/types/auth.go +++ b/pkg/domain/entities/types/auth.go @@ -1,4 +1,5 @@ -package types // import "github.com/docker/docker/api/types" +// copied from github.com/docker/docker/api/types +package types // AuthConfig contains authorization information for connecting to a Registry type AuthConfig struct { diff --git a/pkg/domain/entities/types/types.go b/pkg/domain/entities/types/types.go index 77834c0cb..7dc785078 100644 --- a/pkg/domain/entities/types/types.go +++ b/pkg/domain/entities/types/types.go @@ -1,4 +1,5 @@ -package types // import "github.com/docker/docker/api/types" +// copied from github.com/docker/docker/api/types +package types // ComponentVersion describes the version information for a specific component. type ComponentVersion struct { diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index 9b2fc5280..9a08adf82 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -75,7 +75,25 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { return dangling }) default: - return nil, errors.Errorf("%q is in an invalid volume filter", filter) + return nil, errors.Errorf("%q is an invalid volume filter", filter) + } + } + } + return vf, nil +} + +func GeneratePruneVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { + var vf []libpod.VolumeFilter + for filter, v := range filters { + for _, val := range v { + switch filter { + case "label": + filter := val + vf = append(vf, func(v *libpod.Volume) bool { + return util.MatchLabelFilters([]string{filter}, v.Labels()) + }) + default: + return nil, errors.Errorf("%q is an invalid volume filter", filter) } } } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 24261e5ed..6f8845f10 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -585,7 +585,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrID string, } // If the container is in a pod, also set to recursively start dependencies - err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != "") + err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false) if err != nil && errors.Cause(err) != define.ErrDetach { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } @@ -708,7 +708,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri ctrRunning := ctrState == define.ContainerStateRunning if options.Attach { - err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning, ctr.PodID() != "") + err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning) if errors.Cause(err) == define.ErrDetach { // User manually detached // Exit cleanly immediately @@ -784,7 +784,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri RawInput: rawInput, ExitCode: 125, } - if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { + if err := ctr.Start(ctx, true); err != nil { // if lastError != nil { // fmt.Fprintln(os.Stderr, lastError) // } @@ -845,10 +845,6 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } } - var joinPod bool - if len(ctr.PodID()) > 0 { - joinPod = true - } report := entities.ContainerRunReport{Id: ctr.ID()} if logrus.GetLevel() == logrus.DebugLevel { @@ -859,7 +855,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } if opts.Detach { // if the container was created as part of a pod, also start its dependencies, if any. - if err := ctr.Start(ctx, joinPod); err != nil { + if err := ctr.Start(ctx, true); err != nil { // This means the command did not exist report.ExitCode = define.ExitCode(err) return &report, err @@ -869,7 +865,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } // if the container was created as part of a pod, also start its dependencies, if any. - if err := terminal.StartAttachCtr(ctx, ctr, opts.OutputStream, opts.ErrorStream, opts.InputStream, opts.DetachKeys, opts.SigProxy, true, joinPod); err != nil { + if err := terminal.StartAttachCtr(ctx, ctr, opts.OutputStream, opts.ErrorStream, opts.InputStream, opts.DetachKeys, opts.SigProxy, true); err != nil { // We've manually detached from the container // Do not perform cleanup, or wait for container exit code // Just exit immediately diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index 8de383926..2cabab988 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -177,6 +177,16 @@ func generateRunlabelCommand(runlabel string, img *image.Image, args []string, o return cmd, env, nil } +func replaceName(arg, name string) string { + newarg := strings.ReplaceAll(arg, "$NAME", name) + return strings.ReplaceAll(newarg, "${NAME}", name) +} + +func replaceImage(arg, image string) string { + newarg := strings.ReplaceAll(arg, "$IMAGE", image) + return strings.ReplaceAll(newarg, "${IMAGE}", image) +} + // generateCommand takes a label (string) and converts it to an executable command func generateCommand(command, imageName, name, globalOpts string) ([]string, error) { if name == "" { @@ -196,26 +206,15 @@ func generateCommand(command, imageName, name, globalOpts string) ([]string, err for _, arg := range cmd[1:] { var newArg string switch arg { - case "IMAGE": - newArg = imageName - case "$IMAGE": - newArg = imageName case "IMAGE=IMAGE": newArg = fmt.Sprintf("IMAGE=%s", imageName) - case "IMAGE=$IMAGE": - newArg = fmt.Sprintf("IMAGE=%s", imageName) - case "NAME": - newArg = name case "NAME=NAME": newArg = fmt.Sprintf("NAME=%s", name) - case "NAME=$NAME": - newArg = fmt.Sprintf("NAME=%s", name) - case "$NAME": - newArg = name case "$GLOBAL_OPTS": newArg = globalOpts default: - newArg = arg + newArg = replaceName(arg, name) + newArg = replaceImage(newArg, imageName) } newCommand = append(newCommand, newArg) } diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go index 94f649e15..b0853b554 100644 --- a/pkg/domain/infra/abi/generate.go +++ b/pkg/domain/infra/abi/generate.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "strings" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" @@ -43,120 +44,174 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { var ( - pods []*libpod.Pod - ctrs []*libpod.Container - kubePods []*k8sAPI.Pod - kubeServices []k8sAPI.Service - content []byte + pods []*libpod.Pod + ctrs []*libpod.Container + vols []*libpod.Volume + podContent [][]byte + content [][]byte ) + + // Lookup for podman objects. for _, nameOrID := range nameOrIDs { - // Get the container in question + // Let's assume it's a container, so get the container. ctr, err := ic.Libpod.LookupContainer(nameOrID) if err != nil { - pod, err := ic.Libpod.LookupPod(nameOrID) - if err != nil { + if !strings.Contains(err.Error(), "no such container") { return nil, err } - pods = append(pods, pod) } else { if len(ctr.Dependencies()) > 0 { return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies") } - // we cannot deal with ctrs already in a pod + // we cannot deal with ctrs already in a pod. if len(ctr.PodID()) > 0 { return nil, errors.Errorf("container %s is associated with pod %s: use generate on the pod itself", ctr.ID(), ctr.PodID()) } ctrs = append(ctrs, ctr) + continue + } + + // Maybe it's a pod. + pod, err := ic.Libpod.LookupPod(nameOrID) + if err != nil { + if !strings.Contains(err.Error(), "no such pod") { + return nil, err + } + } else { + pods = append(pods, pod) + continue + } + + // Or volume. + vol, err := ic.Libpod.LookupVolume(nameOrID) + if err != nil { + if !strings.Contains(err.Error(), "no such volume") { + return nil, err + } + } else { + vols = append(vols, vol) + continue } + + // If it reaches here is because the name or id did not exist. + return nil, errors.Errorf("Name or ID %q not found", nameOrID) } - // check our inputs - if len(pods) > 0 && len(ctrs) > 0 { - return nil, errors.New("cannot generate pods and containers at the same time") + // Generate kube persistent volume claims from volumes. + if len(vols) >= 1 { + pvs, err := getKubePVCs(vols) + if err != nil { + return nil, err + } + + content = append(content, pvs...) } + // Generate kube pods and services from pods. if len(pods) >= 1 { pos, svcs, err := getKubePods(pods, options.Service) if err != nil { return nil, err } - kubePods = append(kubePods, pos...) + podContent = append(podContent, pos...) if options.Service { - kubeServices = append(kubeServices, svcs...) + content = append(content, svcs...) } - } else { + } + + // Generate the kube pods from containers. + if len(ctrs) >= 1 { po, err := libpod.GenerateForKube(ctrs) if err != nil { return nil, err } - kubePods = append(kubePods, po) + b, err := generateKubeYAML(po) + if err != nil { + return nil, err + } + + podContent = append(podContent, b) if options.Service { - kubeServices = append(kubeServices, libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{})) + b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{})) + if err != nil { + return nil, err + } + content = append(content, b) } } - content, err := generateKubeOutput(kubePods, kubeServices, options.Service) + // Content order is based on helm install order (secret, persistentVolumeClaim, service, pod). + content = append(content, podContent...) + + // Generate kube YAML file from all kube kinds. + k, err := generateKubeOutput(content) if err != nil { return nil, err } - return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil + return &entities.GenerateKubeReport{Reader: bytes.NewReader(k)}, nil } -func getKubePods(pods []*libpod.Pod, getService bool) ([]*k8sAPI.Pod, []k8sAPI.Service, error) { - kubePods := make([]*k8sAPI.Pod, 0) - kubeServices := make([]k8sAPI.Service, 0) +// getKubePods returns kube pod and service YAML files from podman pods. +func getKubePods(pods []*libpod.Pod, getService bool) ([][]byte, [][]byte, error) { + pos := [][]byte{} + svcs := [][]byte{} for _, p := range pods { - po, svc, err := p.GenerateForKube() + po, sp, err := p.GenerateForKube() + if err != nil { + return nil, nil, err + } + + b, err := generateKubeYAML(po) if err != nil { return nil, nil, err } + pos = append(pos, b) - kubePods = append(kubePods, po) if getService { - kubeServices = append(kubeServices, libpod.GenerateKubeServiceFromV1Pod(po, svc)) + b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, sp)) + if err != nil { + return nil, nil, err + } + svcs = append(svcs, b) } } - return kubePods, kubeServices, nil + return pos, svcs, nil } -func generateKubeOutput(kubePods []*k8sAPI.Pod, kubeServices []k8sAPI.Service, hasService bool) ([]byte, error) { - output := make([]byte, 0) - marshalledPods := make([]byte, 0) - marshalledServices := make([]byte, 0) +// getKubePVCs returns kube persistent volume claim YAML files from podman volumes. +func getKubePVCs(volumes []*libpod.Volume) ([][]byte, error) { + pvs := [][]byte{} - for i, p := range kubePods { - if i != 0 { - marshalledPods = append(marshalledPods, []byte("---\n")...) - } - - b, err := yaml.Marshal(p) + for _, v := range volumes { + b, err := generateKubeYAML(v.GenerateForKube()) if err != nil { return nil, err } - - marshalledPods = append(marshalledPods, b...) + pvs = append(pvs, b) } - if hasService { - for i, s := range kubeServices { - if i != 0 { - marshalledServices = append(marshalledServices, []byte("---\n")...) - } - - b, err := yaml.Marshal(s) - if err != nil { - return nil, err - } + return pvs, nil +} - marshalledServices = append(marshalledServices, b...) - } +// generateKubeYAML marshalls a kube kind into a YAML file. +func generateKubeYAML(kubeKind interface{}) ([]byte, error) { + b, err := yaml.Marshal(kubeKind) + if err != nil { + return nil, err } + return b, nil +} + +// generateKubeOutput generates kube YAML file containing multiple kube kinds. +func generateKubeOutput(content [][]byte) ([]byte, error) { + output := make([]byte, 0) + header := `# Generation of Kubernetes YAML is still under development! # # Save the output of this file and use kubectl create -f to import @@ -169,13 +224,18 @@ func generateKubeOutput(kubePods []*k8sAPI.Pod, kubeServices []k8sAPI.Service, h return nil, err } + // Add header to kube YAML file. output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...) - // kube generate order is based on helm install order (service, pod...) - if hasService { - output = append(output, marshalledServices...) - output = append(output, []byte("---\n")...) + + // kube generate order is based on helm install order (secret, persistentVolume, service, pod...). + // Add kube kinds. + for i, b := range content { + if i != 0 { + output = append(output, []byte("---\n")...) + } + + output = append(output, b...) } - output = append(output, marshalledPods...) return output, nil } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 3b5c141d7..4a13a8029 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "os" + "strconv" "strings" "github.com/containers/common/pkg/secrets" @@ -15,6 +16,7 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" "github.com/containers/podman/v3/pkg/specgen/generate/kube" "github.com/containers/podman/v3/pkg/util" @@ -43,6 +45,14 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en return nil, err } + // sort kube kinds + documentList, err = sortKubeKinds(documentList) + if err != nil { + return nil, errors.Wrapf(err, "unable to sort kube kinds in %q", path) + } + + ipIndex := 0 + // create pod on each document if it is a pod or deployment // any other kube kind will be skipped for _, document := range documentList { @@ -63,7 +73,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en podTemplateSpec.ObjectMeta = podYAML.ObjectMeta podTemplateSpec.Spec = podYAML.Spec - r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options) + r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex) if err != nil { return nil, err } @@ -77,13 +87,27 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Deployment", path) } - r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options) + r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options, &ipIndex) if err != nil { return nil, err } report.Pods = append(report.Pods, r.Pods...) validKinds++ + case "PersistentVolumeClaim": + var pvcYAML v1.PersistentVolumeClaim + + if err := yaml.Unmarshal(document, &pvcYAML); err != nil { + return nil, errors.Wrapf(err, "unable to read YAML %q as Kube PersistentVolumeClaim", path) + } + + r, err := ic.playKubePVC(ctx, &pvcYAML, options) + if err != nil { + return nil, err + } + + report.Volumes = append(report.Volumes, r.Volumes...) + validKinds++ default: logrus.Infof("kube kind %s not supported", kind) continue @@ -97,7 +121,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en return report, nil } -func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { +func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) { var ( deploymentName string podSpec v1.PodTemplateSpec @@ -119,7 +143,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM // create "replicas" number of pods for i = 0; i < numReplicas; i++ { podName := fmt.Sprintf("%s-pod-%d", deploymentName, i) - podReport, err := ic.playKubePod(ctx, podName, &podSpec, options) + podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex) if err != nil { return nil, errors.Wrapf(err, "error encountered while bringing up pod %s", podName) } @@ -128,7 +152,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM return &report, nil } -func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { +func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) { var ( registryCreds *types.DockerAuthConfig writer io.Writer @@ -169,9 +193,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // networks. networks := strings.Split(options.Network, ",") logrus.Debugf("Pod joining CNI networks: %v", networks) + p.NetNS.NSMode = specgen.Bridge p.CNINetworks = append(p.CNINetworks, networks...) } } + if len(options.StaticIPs) > *ipIndex { + p.StaticIP = &options.StaticIPs[*ipIndex] + *ipIndex++ + } else if len(options.StaticIPs) > 0 { + // only warn if the user has set at least one ip ip + logrus.Warn("No more static ips left using a random one") + } // Create the Pod pod, err := generate.MakePod(p, ic.Libpod) @@ -279,6 +311,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY RestartPolicy: ctrRestartPolicy, NetNSIsHost: p.NetNS.IsHost(), SecretsManager: secretsManager, + LogDriver: options.LogDriver, } specGen, err := kube.ToSpecGen(ctx, &specgenOpts) if err != nil { @@ -313,6 +346,68 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY return &report, nil } +// playKubePVC creates a podman volume from a kube persistent volume claim. +func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.PersistentVolumeClaim, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { + var report entities.PlayKubeReport + opts := make(map[string]string) + + // Get pvc name. + // This is the only required pvc attribute to create a podman volume. + name := pvcYAML.GetName() + if strings.TrimSpace(name) == "" { + return nil, fmt.Errorf("persistent volume claim name can not be empty") + } + + // Create podman volume options. + volOptions := []libpod.VolumeCreateOption{ + libpod.WithVolumeName(name), + libpod.WithVolumeLabels(pvcYAML.GetLabels()), + } + + // Get pvc annotations and create remaining podman volume options if available. + // These are podman volume options that do not match any of the persistent volume claim + // attributes, so they can be configured using annotations since they will not affect k8s. + for k, v := range pvcYAML.GetAnnotations() { + switch k { + case util.VolumeDriverAnnotation: + volOptions = append(volOptions, libpod.WithVolumeDriver(v)) + case util.VolumeDeviceAnnotation: + opts["device"] = v + case util.VolumeTypeAnnotation: + opts["type"] = v + case util.VolumeUIDAnnotation: + uid, err := strconv.Atoi(v) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert uid %s to integer", v) + } + volOptions = append(volOptions, libpod.WithVolumeUID(uid)) + opts["UID"] = v + case util.VolumeGIDAnnotation: + gid, err := strconv.Atoi(v) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert gid %s to integer", v) + } + volOptions = append(volOptions, libpod.WithVolumeGID(gid)) + opts["GID"] = v + case util.VolumeMountOptsAnnotation: + opts["o"] = v + } + } + volOptions = append(volOptions, libpod.WithVolumeOptions(opts)) + + // Create volume. + vol, err := ic.Libpod.NewVolume(ctx, volOptions...) + if err != nil { + return nil, err + } + + report.Volumes = append(report.Volumes, entities.PlayKubeVolume{ + Name: vol.Name(), + }) + + return &report, nil +} + // readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) { var cm v1.ConfigMap @@ -374,3 +469,25 @@ func getKubeKind(obj []byte) (string, error) { return kubeObject.Kind, nil } + +// sortKubeKinds adds the correct creation order for the kube kinds. +// Any pod dependecy will be created first like volumes, secrets, etc. +func sortKubeKinds(documentList [][]byte) ([][]byte, error) { + var sortedDocumentList [][]byte + + for _, document := range documentList { + kind, err := getKubeKind(document) + if err != nil { + return nil, err + } + + switch kind { + case "Pod", "Deployment": + sortedDocumentList = append(sortedDocumentList, document) + default: + sortedDocumentList = append([][]byte{document}, sortedDocumentList...) + } + } + + return sortedDocumentList, nil +} diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index a3e753384..f87f9e370 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -390,13 +390,25 @@ func unshareEnv(graphroot, runroot string) []string { fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot)) } -func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error { - cmd := exec.Command(args[0], args[1:]...) - cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() +func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error { + unshare := func() error { + cmd := exec.Command(args[0], args[1:]...) + cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + } + + if options.RootlessCNI { + rootlesscni, err := ic.Libpod.GetRootlessCNINetNs(true) + if err != nil { + return err + } + defer rootlesscni.Cleanup(ic.Libpod) + return rootlesscni.Do(unshare) + } + return unshare() } func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) { diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go index 7a0c2907c..ab71f8f6f 100644 --- a/pkg/domain/infra/abi/terminal/terminal_linux.go +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -39,7 +39,7 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpo // StartAttachCtr starts and (if required) attaches to a container // if you change the signature of this function from os.File to io.Writer, it will trigger a downstream // error. we may need to just lint disable this one. -func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer +func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool) error { //nolint-interfacer resize := make(chan define.TerminalSize) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) @@ -88,7 +88,7 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, return ctr.Attach(streams, detachKeys, resize) } - attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive) + attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, true) if err != nil { return err } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index a0f65f11f..4545d266b 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -629,7 +629,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if opts.Detach { // Detach and return early - err := containers.Start(ic.ClientCtx, con.ID, nil) + err := containers.Start(ic.ClientCtx, con.ID, new(containers.StartOptions).WithRecursive(true)) if err != nil { report.ExitCode = define.ExitCode(err) } diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go index adf34460c..7e59e44c2 100644 --- a/pkg/domain/infra/tunnel/network.go +++ b/pkg/domain/infra/tunnel/network.go @@ -92,5 +92,6 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string // Network prune removes unused cni networks func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) { - return network.Prune(ic.ClientCtx, nil) + opts := new(network.PruneOptions).WithFilters(options.Filters) + return network.Prune(ic.ClientCtx, opts) } diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go index 9f9076114..e52e1a1f7 100644 --- a/pkg/domain/infra/tunnel/play.go +++ b/pkg/domain/infra/tunnel/play.go @@ -11,7 +11,7 @@ import ( func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password) options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps) - options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot) + options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot).WithStaticIPs(opts.StaticIPs) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { options.WithSkipTLSVerify(s == types.OptionalBoolTrue) diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go index d2c5063c9..7400d3771 100644 --- a/pkg/domain/infra/tunnel/system.go +++ b/pkg/domain/infra/tunnel/system.go @@ -28,7 +28,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System return system.DiskUsage(ic.ClientCtx, nil) } -func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error { +func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error { return errors.New("unshare is not supported on remote clients") } diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go index b1923be98..9dc545ebb 100644 --- a/pkg/errorhandling/errorhandling.go +++ b/pkg/errorhandling/errorhandling.go @@ -24,6 +24,9 @@ func JoinErrors(errs []error) error { if finalErr == nil { return finalErr } + if len(multiE.WrappedErrors()) == 1 && logrus.IsLevelEnabled(logrus.TraceLevel) { + return multiE.WrappedErrors()[0] + } return errors.New(strings.TrimSpace(finalErr.Error())) } diff --git a/pkg/registrar/registrar.go b/pkg/registrar/registrar.go deleted file mode 100644 index 1e75ee995..000000000 --- a/pkg/registrar/registrar.go +++ /dev/null @@ -1,127 +0,0 @@ -// Package registrar provides name registration. It reserves a name to a given key. -package registrar - -import ( - "errors" - "sync" -) - -var ( - // ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved - ErrNameReserved = errors.New("name is reserved") - // ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved - ErrNameNotReserved = errors.New("name is not reserved") - // ErrNoSuchKey is returned when trying to find the names for a key which is not known - ErrNoSuchKey = errors.New("provided key does not exist") -) - -// Registrar stores indexes a list of keys and their registered names as well as indexes names and the key that they are registered to -// Names must be unique. -// Registrar is safe for concurrent access. -type Registrar struct { - idx map[string][]string - names map[string]string - mu sync.Mutex -} - -// NewRegistrar creates a new Registrar with the an empty index -func NewRegistrar() *Registrar { - return &Registrar{ - idx: make(map[string][]string), - names: make(map[string]string), - } -} - -// Reserve registers a key to a name -// Reserve is idempotent -// Attempting to reserve a key to a name that already exists results in an `ErrNameReserved` -// A name reservation is globally unique -func (r *Registrar) Reserve(name, key string) error { - r.mu.Lock() - defer r.mu.Unlock() - - if k, exists := r.names[name]; exists { - if k != key { - return ErrNameReserved - } - return nil - } - - r.idx[key] = append(r.idx[key], name) - r.names[name] = key - return nil -} - -// Release releases the reserved name -// Once released, a name can be reserved again -func (r *Registrar) Release(name string) { - r.mu.Lock() - defer r.mu.Unlock() - - key, exists := r.names[name] - if !exists { - return - } - - for i, n := range r.idx[key] { - if n != name { - continue - } - r.idx[key] = append(r.idx[key][:i], r.idx[key][i+1:]...) - break - } - - delete(r.names, name) - - if len(r.idx[key]) == 0 { - delete(r.idx, key) - } -} - -// Delete removes all reservations for the passed in key. -// All names reserved to this key are released. -func (r *Registrar) Delete(key string) { - r.mu.Lock() - for _, name := range r.idx[key] { - delete(r.names, name) - } - delete(r.idx, key) - r.mu.Unlock() -} - -// GetNames lists all the reserved names for the given key -func (r *Registrar) GetNames(key string) ([]string, error) { - r.mu.Lock() - defer r.mu.Unlock() - - names, exists := r.idx[key] - if !exists { - return nil, ErrNoSuchKey - } - return names, nil -} - -// Get returns the key that the passed in name is reserved to -func (r *Registrar) Get(name string) (string, error) { - r.mu.Lock() - key, exists := r.names[name] - r.mu.Unlock() - - if !exists { - return "", ErrNameNotReserved - } - return key, nil -} - -// GetAll returns all registered names -func (r *Registrar) GetAll() map[string][]string { - out := make(map[string][]string) - - r.mu.Lock() - // copy index into out - for id, names := range r.idx { - out[id] = names - } - r.mu.Unlock() - return out -} diff --git a/pkg/registrar/registrar_test.go b/pkg/registrar/registrar_test.go deleted file mode 100644 index dc9942e80..000000000 --- a/pkg/registrar/registrar_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package registrar_test - -import ( - "testing" - - "github.com/containers/podman/v3/pkg/registrar" - . "github.com/containers/podman/v3/test/framework" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -// TestRegistrar runs the created specs -func TestRegistrar(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Registrar") -} - -// nolint: gochecknoglobals -var t *TestFramework - -var _ = BeforeSuite(func() { - t = NewTestFramework(NilFunc, NilFunc) - t.Setup() -}) - -var _ = AfterSuite(func() { - t.Teardown() -}) - -// The actual test suite -var _ = t.Describe("Registrar", func() { - // Constant test data needed by some tests - const ( - testKey = "testKey" - testName = "testName" - anotherKey = "anotherKey" - ) - - // The system under test - var sut *registrar.Registrar - - // Prepare the system under test and register a test name and key before - // each test - BeforeEach(func() { - sut = registrar.NewRegistrar() - Expect(sut.Reserve(testName, testKey)).To(BeNil()) - }) - - t.Describe("Reserve", func() { - It("should succeed to reserve a new registrar", func() { - // Given - // When - err := sut.Reserve("name", "key") - - // Then - Expect(err).To(BeNil()) - }) - - It("should succeed to reserve a registrar twice", func() { - // Given - // When - err := sut.Reserve(testName, testKey) - - // Then - Expect(err).To(BeNil()) - }) - - It("should fail to reserve an already reserved registrar", func() { - // Given - // When - err := sut.Reserve(testName, anotherKey) - - // Then - Expect(err).NotTo(BeNil()) - Expect(err).To(Equal(registrar.ErrNameReserved)) - }) - }) - - t.Describe("Release", func() { - It("should succeed to release a registered registrar multiple times", func() { - // Given - // When - // Then - sut.Release(testName) - sut.Release(testName) - }) - - It("should succeed to release a unknown registrar multiple times", func() { - // Given - // When - // Then - sut.Release(anotherKey) - sut.Release(anotherKey) - }) - - It("should succeed to release and re-register a registrar", func() { - // Given - // When - sut.Release(testName) - err := sut.Reserve(testName, testKey) - - // Then - Expect(err).To(BeNil()) - }) - }) - - t.Describe("GetNames", func() { - It("should succeed to retrieve a single name for a registrar", func() { - // Given - // When - names, err := sut.GetNames(testKey) - - // Then - Expect(err).To(BeNil()) - Expect(len(names)).To(Equal(1)) - Expect(names[0]).To(Equal(testName)) - }) - - It("should succeed to retrieve all names for a registrar", func() { - // Given - testNames := []string{"test1", "test2"} - for _, name := range testNames { - Expect(sut.Reserve(name, anotherKey)).To(BeNil()) - } - - // When - names, err := sut.GetNames(anotherKey) - - // Then - Expect(err).To(BeNil()) - Expect(len(names)).To(Equal(2)) - Expect(names).To(Equal(testNames)) - }) - }) - - t.Describe("GetNames", func() { - It("should succeed to retrieve a single name for a registrar", func() { - // Given - // When - names, err := sut.GetNames(testKey) - - // Then - Expect(err).To(BeNil()) - Expect(len(names)).To(Equal(1)) - Expect(names[0]).To(Equal(testName)) - }) - - It("should succeed to retrieve all names for a registrar", func() { - // Given - anotherKey := "anotherKey" - testNames := []string{"test1", "test2"} - for _, name := range testNames { - Expect(sut.Reserve(name, anotherKey)).To(BeNil()) - } - - // When - names, err := sut.GetNames(anotherKey) - - // Then - Expect(err).To(BeNil()) - Expect(len(names)).To(Equal(2)) - Expect(names).To(Equal(testNames)) - }) - }) - - t.Describe("Delete", func() { - It("should succeed to delete a registrar", func() { - // Given - // When - sut.Delete(testKey) - - // Then - names, err := sut.GetNames(testKey) - Expect(len(names)).To(BeZero()) - Expect(err).To(Equal(registrar.ErrNoSuchKey)) - }) - }) - - t.Describe("Get", func() { - It("should succeed to get a key for a registrar", func() { - // Given - // When - key, err := sut.Get(testName) - - // Then - Expect(err).To(BeNil()) - Expect(key).To(Equal(testKey)) - }) - - It("should fail to get a key for a not existing registrar", func() { - // Given - // When - key, err := sut.Get("notExistingName") - - // Then - Expect(key).To(BeEmpty()) - Expect(err).To(Equal(registrar.ErrNameNotReserved)) - }) - }) - - t.Describe("GetAll", func() { - It("should succeed to get all names", func() { - // Given - // When - names := sut.GetAll() - - // Then - Expect(len(names)).To(Equal(1)) - Expect(len(names[testKey])).To(Equal(1)) - Expect(names[testKey][0]).To(Equal(testName)) - }) - }) -}) diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 1d724ffb0..ef9975021 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -364,6 +364,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if len(s.Secrets) != 0 { options = append(options, libpod.WithSecrets(s.Secrets)) } + if len(s.DependencyContainers) > 0 { + deps := make([]*libpod.Container, 0, len(s.DependencyContainers)) + for _, ctr := range s.DependencyContainers { + depCtr, err := rt.LookupContainer(ctr) + if err != nil { + return nil, errors.Wrapf(err, "%q is not a valid container, cannot be used as a dependency", ctr) + } + deps = append(deps, depCtr) + } + options = append(options, libpod.WithDependencyCtrs(deps)) + } return options, nil } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 31ed3fd7c..7aeec9d41 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -98,6 +98,8 @@ type CtrSpecGenOptions struct { NetNSIsHost bool // SecretManager to access the secrets SecretsManager *secrets.SecretsManager + // LogDriver which should be used for the container + LogDriver string } func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGenerator, error) { @@ -115,6 +117,10 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener s.Pod = opts.PodID + s.LogConfiguration = &specgen.LogConfig{ + Driver: opts.LogDriver, + } + setupSecurityContext(s, opts.Container) // Since we prefix the container name with pod name to work-around the uniqueness requirement, diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index e135f4728..8066834f7 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -57,10 +57,13 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru } for _, m := range s.Mounts { - if _, ok := unifiedMounts[m.Destination]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination) + // Ensure that mount dest is clean, so that it can be + // compared against named volumes and avoid duplicate mounts. + cleanDestination := filepath.Clean(m.Destination) + if _, ok := unifiedMounts[cleanDestination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", cleanDestination) } - unifiedMounts[m.Destination] = m + unifiedMounts[cleanDestination] = m } for _, m := range commonMounts { diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index c10dc5ef5..28111f96d 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -160,10 +160,17 @@ type ContainerBasicConfig struct { // to 0, 1, 2) that will be passed to the executed process. The total FDs // passed will be 3 + PreserveFDs. // set tags as `json:"-"` for not supported remote + // Optional. PreserveFDs uint `json:"-"` // Timezone is the timezone inside the container. // Local means it has the same timezone as the host machine + // Optional. Timezone string `json:"timezone,omitempty"` + // DependencyContainers is an array of containers this container + // depends on. Dependency containers must be started before this + // container. Dependencies can be specified by name or full/partial ID. + // Optional. + DependencyContainers []string `json:"dependencyContainers,omitempty"` } // ContainerStorageConfig contains information on the storage configuration of a diff --git a/pkg/util/kube.go b/pkg/util/kube.go new file mode 100644 index 000000000..1255cdfc5 --- /dev/null +++ b/pkg/util/kube.go @@ -0,0 +1,16 @@ +package util + +const ( + // Kube annotation for podman volume driver. + VolumeDriverAnnotation = "volume.podman.io/driver" + // Kube annotation for podman volume type. + VolumeTypeAnnotation = "volume.podman.io/type" + // Kube annotation for podman volume device. + VolumeDeviceAnnotation = "volume.podman.io/device" + // Kube annotation for podman volume UID. + VolumeUIDAnnotation = "volume.podman.io/uid" + // Kube annotation for podman volume GID. + VolumeGIDAnnotation = "volume.podman.io/gid" + // Kube annotation for podman volume mount options. + VolumeMountOptsAnnotation = "volume.podman.io/mount-options" +) |