diff options
Diffstat (limited to 'pkg')
28 files changed, 368 insertions, 229 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 8e9e1fb39..0b5cbd343 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -71,13 +71,12 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { imgNameOrID := newImage.ID() // if the img had multi names with the same sha256 ID, should use the InputName, not the ID if len(newImage.Names()) > 1 { - imageRef, err := utils.ParseDockerReference(resolvedName) - if err != nil { + if err := utils.IsRegistryReference(resolvedName); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } // maybe the InputName has no tag, so use full name to display - imgNameOrID = imageRef.DockerReference().String() + imgNameOrID = resolvedName } sg := specgen.NewSpecGenerator(imgNameOrID, cliOpts.RootFS) diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go index 694b57bb1..851955207 100644 --- a/pkg/api/handlers/compat/containers_stats.go +++ b/pkg/api/handlers/compat/containers_stats.go @@ -22,7 +22,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Stream bool `schema:"stream"` + Stream bool `schema:"stream"` + OneShot bool `schema:"one-shot"` //added schema for one shot }{ Stream: true, } @@ -30,6 +31,10 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } + if query.Stream && query.OneShot { // mismatch. one-shot can only be passed with stream=false + utils.Error(w, "invalid combination of stream and one-shot", http.StatusBadRequest, define.ErrInvalidArg) + return + } name := utils.GetName(r) ctnr, err := runtime.LookupContainer(name) @@ -56,6 +61,16 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { return } + coder := json.NewEncoder(w) + // Write header and content type. + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + + // Setup JSON encoder for streaming. + coder.SetEscapeHTML(true) var preRead time.Time var preCPUStats CPUStats if query.Stream { @@ -75,17 +90,6 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { } } - // Write header and content type. - w.WriteHeader(http.StatusOK) - w.Header().Add("Content-Type", "application/json") - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } - - // Setup JSON encoder for streaming. - coder := json.NewEncoder(w) - coder.SetEscapeHTML(true) - streamLabel: // A label to flatten the scope select { case <-r.Context().Done(): @@ -199,7 +203,7 @@ streamLabel: // A label to flatten the scope flusher.Flush() } - if !query.Stream { + if !query.Stream || query.OneShot { return } diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 405e616c5..9fbac91e0 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -75,7 +75,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { coder := json.NewEncoder(w) coder.SetEscapeHTML(true) - for stream := true; stream; stream = query.Stream { + for { select { case err := <-errorChannel: if err != nil { diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 3f4320efa..7b336c470 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -1,7 +1,6 @@ package compat import ( - "context" "encoding/json" "fmt" "io/ioutil" @@ -13,12 +12,12 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" - "github.com/containers/podman/v3/pkg/channel" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/storage" @@ -210,6 +209,11 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { }) } +type pullResult struct { + images []*libimage.Image + err error +} + func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { // 200 no error // 404 repo does not exist or no read access @@ -231,6 +235,14 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { fromImage := mergeNameAndTagOrDigest(query.FromImage, query.Tag) + // without this early check this function would return 200 but reported error via body stream soon after + // it's better to let caller know early via HTTP status code that request cannot be processed + _, err := shortnames.Resolve(runtime.SystemContext(), fromImage) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrap(err, "failed to resolve image name")) + return + } + authConf, authfile, key, err := auth.GetCredentials(r) if err != nil { utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) @@ -247,26 +259,14 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { } pullOptions.Writer = os.Stderr // allows for debugging on the server - stderr := channel.NewWriter(make(chan []byte)) - defer stderr.Close() - progress := make(chan types.ProgressProperties) + pullOptions.Progress = progress - var img string - runCtx, cancel := context.WithCancel(context.Background()) + pullResChan := make(chan pullResult) go func() { - defer cancel() - pulledImages, err := runtime.LibimageRuntime().Pull(runCtx, fromImage, config.PullPolicyAlways, pullOptions) - if err != nil { - stderr.Write([]byte(err.Error() + "\n")) - } else { - if len(pulledImages) == 0 { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.New("internal error: no images pulled")) - return - } - img = pulledImages[0].ID() - } + pulledImages, err := runtime.LibimageRuntime().Pull(r.Context(), fromImage, config.PullPolicyAlways, pullOptions) + pullResChan <- pullResult{images: pulledImages, err: err} }() flush := func() { @@ -281,7 +281,6 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.SetEscapeHTML(true) - var failed bool loop: // break out of for/select infinite loop for { @@ -312,32 +311,31 @@ loop: // break out of for/select infinite loop } report.Id = e.Artifact.Digest.Encoded()[0:12] if err := enc.Encode(report); err != nil { - stderr.Write([]byte(err.Error())) - } - flush() - case e := <-stderr.Chan(): - failed = true - report.Error = string(e) - if err := enc.Encode(report); err != nil { logrus.Warnf("Failed to json encode error %q", err.Error()) } flush() - case <-runCtx.Done(): - if !failed { - if utils.IsLibpodRequest(r) { - report.Status = "Pull complete" + case pullRes := <-pullResChan: + err := pullRes.err + pulledImages := pullRes.images + if err != nil { + report.Error = err.Error() + } else { + if len(pulledImages) > 0 { + img := pulledImages[0].ID() + if utils.IsLibpodRequest(r) { + report.Status = "Pull complete" + } else { + report.Status = "Download complete" + } + report.Id = img[0:12] } else { - report.Status = "Download complete" - } - report.Id = img[0:12] - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) + report.Error = "internal error: no images pulled" } - flush() } - break loop // break out of for/select infinite loop - case <-r.Context().Done(): - // Client has closed connection + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to json encode error %q", err.Error()) + } + flush() break loop // break out of for/select infinite loop } } diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go index db02af445..62f8cdc77 100644 --- a/pkg/api/handlers/compat/images_push.go +++ b/pkg/api/handlers/compat/images_push.go @@ -1,7 +1,6 @@ package compat import ( - "context" "encoding/json" "fmt" "io/ioutil" @@ -12,7 +11,6 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" - "github.com/containers/podman/v3/pkg/channel" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/storage" @@ -101,46 +99,33 @@ func PushImage(w http.ResponseWriter, r *http.Request) { destination = imageName } - errorWriter := channel.NewWriter(make(chan []byte)) - defer errorWriter.Close() - - statusWriter := channel.NewWriter(make(chan []byte)) - defer statusWriter.Close() - - runCtx, cancel := context.WithCancel(context.Background()) - var failed bool - - go func() { - defer cancel() - - statusWriter.Write([]byte(fmt.Sprintf("The push refers to repository [%s]", imageName))) - - err := imageEngine.Push(runCtx, imageName, destination, options) - if err != nil { - if errors.Cause(err) != storage.ErrImageUnknown { - errorWriter.Write([]byte("An image does not exist locally with the tag: " + imageName)) - } else { - errorWriter.Write([]byte(err.Error())) - } - } - }() - - flush := func() { - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } + flush := func() {} + if flusher, ok := w.(http.Flusher); ok { + flush = flusher.Flush } w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") flush() + var report jsonmessage.JSONMessage enc := json.NewEncoder(w) enc.SetEscapeHTML(true) + report.Status = fmt.Sprintf("The push refers to repository [%s]", imageName) + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to json encode error %q", err.Error()) + } + flush() + + pushErrChan := make(chan error) + go func() { + pushErrChan <- imageEngine.Push(r.Context(), imageName, destination, options) + }() + loop: // break out of for/select infinite loop for { - var report jsonmessage.JSONMessage + report = jsonmessage.JSONMessage{} select { case e := <-options.Progress: @@ -160,43 +145,50 @@ loop: // break out of for/select infinite loop } report.ID = e.Artifact.Digest.Encoded()[0:12] if err := enc.Encode(report); err != nil { - errorWriter.Write([]byte(err.Error())) + logrus.Warnf("Failed to json encode error %q", err.Error()) } flush() - case e := <-statusWriter.Chan(): - report.Status = string(e) - if err := enc.Encode(report); err != nil { - errorWriter.Write([]byte(err.Error())) + case err := <-pushErrChan: + if err != nil { + var msg string + if errors.Cause(err) != storage.ErrImageUnknown { + msg = "An image does not exist locally with the tag: " + imageName + } else { + msg = err.Error() + } + report.Error = &jsonmessage.JSONError{ + Message: msg, + } + report.ErrorMessage = msg + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to json encode error %q", err.Error()) + } + flush() + break loop } - flush() - case e := <-errorWriter.Chan(): - failed = true - report.Error = &jsonmessage.JSONError{ - Message: string(e), + + digestBytes, err := ioutil.ReadAll(digestFile) + if err != nil { + report.Error = &jsonmessage.JSONError{ + Message: err.Error(), + } + report.ErrorMessage = err.Error() + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to json encode error %q", err.Error()) + } + flush() + break loop } - report.ErrorMessage = string(e) + tag := query.Tag + if tag == "" { + tag = "latest" + } + report.Status = fmt.Sprintf("%s: digest: %s", tag, string(digestBytes)) if err := enc.Encode(report); err != nil { logrus.Warnf("Failed to json encode error %q", err.Error()) } + flush() - case <-runCtx.Done(): - if !failed { - digestBytes, err := ioutil.ReadAll(digestFile) - if err == nil { - tag := query.Tag - if tag == "" { - tag = "latest" - } - report.Status = fmt.Sprintf("%s: digest: %s", tag, string(digestBytes)) - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) - } - flush() - } - } - break loop // break out of for/select infinite loop - case <-r.Context().Done(): - // Client has closed connection break loop // break out of for/select infinite loop } } diff --git a/pkg/api/handlers/compat/resize.go b/pkg/api/handlers/compat/resize.go index 23ed33a22..f65e313fc 100644 --- a/pkg/api/handlers/compat/resize.go +++ b/pkg/api/handlers/compat/resize.go @@ -46,20 +46,13 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) { utils.ContainerNotFound(w, name, err) return } - if state, err := ctnr.State(); err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain container state")) - return - } else if state != define.ContainerStateRunning && !query.IgnoreNotRunning { - utils.Error(w, "Container not running", http.StatusConflict, - fmt.Errorf("container %q in wrong state %q", name, state.String())) - return - } - // If container is not running, ignore since this can be a race condition, and is expected if err := ctnr.AttachResize(sz); err != nil { - if errors.Cause(err) != define.ErrCtrStateInvalid || !query.IgnoreNotRunning { + if errors.Cause(err) != define.ErrCtrStateInvalid { utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container")) - return + } else { + utils.Error(w, "Container not running", http.StatusConflict, err) } + return } // This is not a 204, even though we write nothing, for compatibility // reasons. diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index a90408bfd..fc6ab4b4c 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -482,7 +482,7 @@ func PushImage(w http.ResponseWriter, r *http.Request) { destination = source } - if _, err := utils.ParseDockerReference(destination); err != nil { + if err := utils.IsRegistryReference(destination); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go index 7545ba235..fe56aa31d 100644 --- a/pkg/api/handlers/libpod/images_pull.go +++ b/pkg/api/handlers/libpod/images_pull.go @@ -48,7 +48,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { } // Make sure that the reference has no transport or the docker one. - if _, err := utils.ParseDockerReference(query.Reference); err != nil { + if err := utils.IsRegistryReference(query.Reference); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index f21eb2e80..2f36db583 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -169,7 +169,7 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - if _, err := utils.ParseDockerReference(query.Destination); err != nil { + if err := utils.IsRegistryReference(query.Destination); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index 9450a70d9..19eced986 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -95,7 +95,7 @@ type swagInfoResponse struct { // swagger:response NetworkRmReport type swagNetworkRmReport struct { // in:body - Body entities.NetworkRmReport + Body []entities.NetworkRmReport } // Network inspect diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 2662cd368..2a1908d63 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -15,22 +15,19 @@ import ( "github.com/pkg/errors" ) -// ParseDockerReference parses the specified image name to a -// `types.ImageReference` and enforces it to refer to a docker-transport -// reference. -func ParseDockerReference(name string) (types.ImageReference, error) { - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) +// IsRegistryReference checks if the specified name points to the "docker://" +// transport. If it points to no supported transport, we'll assume a +// non-transport reference pointing to an image (e.g., "fedora:latest"). +func IsRegistryReference(name string) error { imageRef, err := alltransports.ParseImageName(name) - if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { - return nil, errors.Errorf("reference %q must be a docker reference", name) - } else if err != nil { - origErr := err - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, name)) - if err != nil { - return nil, errors.Wrapf(origErr, "reference %q must be a docker reference", name) - } + if err != nil { + // No supported transport -> assume a docker-stype reference. + return nil } - return imageRef, nil + if imageRef.Transport().Name() == docker.Transport.Name() { + return nil + } + return errors.Errorf("unsupport transport %s in %q: only docker transport is supported", imageRef.Transport().Name(), name) } // ParseStorageReference parses the specified image name to a diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go index 28b8706a8..becc674c0 100644 --- a/pkg/api/server/handler_api.go +++ b/pkg/api/server/handler_api.go @@ -63,6 +63,12 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { w.Header().Set("Libpod-API-Version", lv) w.Header().Set("Server", "Libpod/"+lv+" ("+runtime.GOOS+")") + if s.CorsHeaders != "" { + w.Header().Set("Access-Control-Allow-Origin", s.CorsHeaders) + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth, Connection, Upgrade, X-Registry-Config") + w.Header().Set("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS") + } + h(w, r) logrus.Debugf("APIHandler(%s) -- %s %s END", rid, r.Method, r.URL.String()) } diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 536c4707a..88ebb4df5 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -375,6 +375,11 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // type: boolean // default: true // description: Stream the output + // - in: query + // name: one-shot + // type: boolean + // default: false + // description: Provide a one-shot response in which preCPU stats are blank, resulting in a single cycle return. // produces: // - application/json // responses: @@ -1359,6 +1364,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // $ref: "#/responses/ok" // 404: // $ref: "#/responses/NoSuchContainer" + // 409: + // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost) diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 972541bc6..1e8faf8f5 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -34,10 +34,12 @@ type APIServer struct { context.CancelFunc // Stop APIServer idleTracker *idle.Tracker // Track connections to support idle shutdown pprof *http.Server // Sidecar http server for providing performance data + CorsHeaders string // Inject CORS headers to each request } // Number of seconds to wait for next request, if exceeded shutdown server const ( + DefaultCorsHeaders = "" DefaultServiceDuration = 300 * time.Second UnlimitedServiceDuration = 0 * time.Second ) @@ -45,17 +47,22 @@ const ( // shutdownOnce ensures Shutdown() may safely be called from several go routines var shutdownOnce sync.Once +type Options struct { + Timeout time.Duration + CorsHeaders string +} + // NewServer will create and configure a new API server with all defaults func NewServer(runtime *libpod.Runtime) (*APIServer, error) { - return newServer(runtime, DefaultServiceDuration, nil) + return newServer(runtime, DefaultServiceDuration, nil, DefaultCorsHeaders) } // NewServerWithSettings will create and configure a new API server using provided settings -func NewServerWithSettings(runtime *libpod.Runtime, duration time.Duration, listener *net.Listener) (*APIServer, error) { - return newServer(runtime, duration, listener) +func NewServerWithSettings(runtime *libpod.Runtime, listener *net.Listener, opts Options) (*APIServer, error) { + return newServer(runtime, opts.Timeout, listener, opts.CorsHeaders) } -func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Listener) (*APIServer, error) { +func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Listener, corsHeaders string) (*APIServer, error) { // If listener not provided try socket activation protocol if listener == nil { if _, found := os.LookupEnv("LISTEN_PID"); !found { @@ -71,6 +78,11 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li } listener = &listeners[0] } + if corsHeaders == "" { + logrus.Debug("CORS Headers were not set") + } else { + logrus.Debugf("CORS Headers were set to %s", corsHeaders) + } logrus.Infof("API server listening on %q", (*listener).Addr()) router := mux.NewRouter().UseEncodedPath() @@ -88,6 +100,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li idleTracker: idle, Listener: *listener, Runtime: runtime, + CorsHeaders: corsHeaders, } router.NotFoundHandler = http.HandlerFunc( diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index fd8a7011d..adef1e7c8 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -138,7 +138,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri winCtx, winCancel := context.WithCancel(ctx) defer winCancel() - go attachHandleResize(ctx, winCtx, winChange, false, nameOrID, file) + attachHandleResize(ctx, winCtx, winChange, false, nameOrID, file) } // If we are attaching around a start, we need to "signal" @@ -327,32 +327,38 @@ func (f *rawFormatter) Format(entry *logrus.Entry) ([]byte, error) { return append(buffer, '\r'), nil } -// This is intended to be run as a goroutine, handling resizing for a container -// or exec session. +// This is intended to not be run as a goroutine, handling resizing for a container +// or exec session. It will call resize once and then starts a goroutine which calls resize on winChange func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, isExec bool, id string, file *os.File) { - // Prime the pump, we need one reset to ensure everything is ready - winChange <- sig.SIGWINCH - for { - select { - case <-winCtx.Done(): - return - case <-winChange: - w, h, err := terminal.GetSize(int(file.Fd())) - if err != nil { - logrus.Warnf("failed to obtain TTY size: %v", err) - } + resize := func() { + w, h, err := terminal.GetSize(int(file.Fd())) + if err != nil { + logrus.Warnf("failed to obtain TTY size: %v", err) + } - var resizeErr error - if isExec { - resizeErr = ResizeExecTTY(ctx, id, new(ResizeExecTTYOptions).WithHeight(h).WithWidth(w)) - } else { - resizeErr = ResizeContainerTTY(ctx, id, new(ResizeTTYOptions).WithHeight(h).WithWidth(w)) - } - if resizeErr != nil { - logrus.Warnf("failed to resize TTY: %v", resizeErr) - } + var resizeErr error + if isExec { + resizeErr = ResizeExecTTY(ctx, id, new(ResizeExecTTYOptions).WithHeight(h).WithWidth(w)) + } else { + resizeErr = ResizeContainerTTY(ctx, id, new(ResizeTTYOptions).WithHeight(h).WithWidth(w)) + } + if resizeErr != nil { + logrus.Warnf("failed to resize TTY: %v", resizeErr) } } + + resize() + + go func() { + for { + select { + case <-winCtx.Done(): + return + case <-winChange: + resize() + } + } + }() } // Configure the given terminal for raw mode @@ -457,7 +463,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar winCtx, winCancel := context.WithCancel(ctx) defer winCancel() - go attachHandleResize(ctx, winCtx, winChange, true, sessionID, terminalFile) + attachHandleResize(ctx, winCtx, winChange, true, sessionID, terminalFile) } if options.GetAttachInput() { diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index 31a6185dc..cca4bf44e 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -11,9 +11,10 @@ import ( // ServiceOptions provides the input for starting an API Service type ServiceOptions struct { - URI string // Path to unix domain socket service should listen on - Timeout time.Duration // duration of inactivity the service should wait before shutting down - Command *cobra.Command // CLI command provided. Used in V1 code + URI string // Path to unix domain socket service should listen on + Timeout time.Duration // duration of inactivity the service should wait before shutting down + Command *cobra.Command // CLI command provided. Used in V1 code + CorsHeaders string // CORS headers } // SystemPruneOptions provides options to prune system. diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index 33ab280e5..7900caaa6 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -11,7 +11,7 @@ import ( ) func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) { - var reports []*entities.NetworkListReport + reports := make([]*entities.NetworkListReport, 0) config, err := ic.Libpod.GetConfig() if err != nil { diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 652229963..58794ce42 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -32,6 +32,7 @@ var ( ErrVMAlreadyExists = errors.New("VM already exists") ErrVMAlreadyRunning = errors.New("VM already running") ErrMultipleActiveVM = errors.New("only one VM can be active at a time") + ForwarderBinaryName = "gvproxy" ) type Download struct { diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index 00068a136..a5c7210af 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -118,6 +118,7 @@ func getDirs(usrName string) []Directory { // in one swoop, then the leading dirs are creates as root. newDirs := []string{ "/home/" + usrName + "/.config", + "/home/" + usrName + "/.config/containers", "/home/" + usrName + "/.config/systemd", "/home/" + usrName + "/.config/systemd/user", "/home/" + usrName + "/.config/systemd/user/default.target.wants", @@ -159,6 +160,22 @@ func getFiles(usrName string) []File { }, }) + // Set containers.conf up for core user to use cni networks + // by default + files = append(files, File{ + Node: Node{ + Group: getNodeGrp(usrName), + Path: "/home/" + usrName + "/.config/containers/containers.conf", + User: getNodeUsr(usrName), + }, + FileEmbedded1: FileEmbedded1{ + Append: nil, + Contents: Resource{ + Source: strToPtr("data:,%5Bcontainers%5D%0D%0Anetns%3D%22bridge%22%0D%0Arootless_networking%3D%22cni%22"), + }, + Mode: intToPtr(484), + }, + }) // Add a file into linger files = append(files, File{ Node: Node{ diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 0bd711c90..31c355d4a 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -13,6 +13,8 @@ import ( "strings" "time" + "github.com/containers/podman/v3/pkg/rootless" + "github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/utils" "github.com/containers/storage/pkg/homedir" @@ -82,9 +84,10 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) { cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...) // Add network - cmd = append(cmd, "-nic", "user,model=virtio,hostfwd=tcp::"+strconv.Itoa(vm.Port)+"-:22") - - socketPath, err := getSocketDir() + // Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is + // why we can only run one vm at a time right now + cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...) + socketPath, err := getRuntimeDir() if err != nil { return nil, err } @@ -235,12 +238,35 @@ func (v *MachineVM) Init(opts machine.InitOptions) error { // Start executes the qemu command line and forks it func (v *MachineVM) Start(name string, _ machine.StartOptions) error { var ( - conn net.Conn - err error - wait time.Duration = time.Millisecond * 500 + conn net.Conn + err error + qemuSocketConn net.Conn + wait time.Duration = time.Millisecond * 500 ) + if err := v.startHostNetworking(); err != nil { + return errors.Errorf("unable to start host networking: %q", err) + } + qemuSocketPath, _, err := v.getSocketandPid() + + for i := 0; i < 6; i++ { + qemuSocketConn, err = net.Dial("unix", qemuSocketPath) + if err == nil { + break + } + time.Sleep(wait) + wait++ + } + if err != nil { + return err + } + + fd, err := qemuSocketConn.(*net.UnixConn).File() + if err != nil { + return err + } + attr := new(os.ProcAttr) - files := []*os.File{os.Stdin, os.Stdout, os.Stderr} + files := []*os.File{os.Stdin, os.Stdout, os.Stderr, fd} attr.Files = files logrus.Debug(v.CmdLine) cmd := v.CmdLine @@ -256,7 +282,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return err } fmt.Println("Waiting for VM ...") - socketPath, err := getSocketDir() + socketPath, err := getRuntimeDir() if err != nil { return err } @@ -309,16 +335,42 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { logrus.Error(err) } }() - _, err = qmpMonitor.Run(input) - return err + if _, err = qmpMonitor.Run(input); err != nil { + return err + } + _, pidFile, err := v.getSocketandPid() + if err != nil { + return err + } + if _, err := os.Stat(pidFile); os.IsNotExist(err) { + logrus.Infof("pid file %s does not exist", pidFile) + return nil + } + pidString, err := ioutil.ReadFile(pidFile) + if err != nil { + return err + } + pidNum, err := strconv.Atoi(string(pidString)) + if err != nil { + return err + } + + p, err := os.FindProcess(pidNum) + if p == nil && err != nil { + return err + } + return p.Kill() } // NewQMPMonitor creates the monitor subsection of our vm func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) { - rtDir, err := getSocketDir() + rtDir, err := getRuntimeDir() if err != nil { return Monitor{}, err } + if !rootless.IsRootless() { + rtDir = "/run" + } rtDir = filepath.Join(rtDir, "podman") if _, err := os.Stat(filepath.Join(rtDir)); os.IsNotExist(err) { // TODO 0644 is fine on linux but macos is weird @@ -533,3 +585,47 @@ func CheckActiveVM() (bool, string, error) { } return false, "", nil } + +// startHostNetworking runs a binary on the host system that allows users +// to setup port forwarding to the podman virtual machine +func (v *MachineVM) startHostNetworking() error { + binary, err := exec.LookPath(machine.ForwarderBinaryName) + if err != nil { + return err + } + // Listen on all at port 7777 for setting up and tearing + // down forwarding + listenSocket := "tcp://0.0.0.0:7777" + qemuSocket, pidFile, err := v.getSocketandPid() + if err != nil { + return err + } + attr := new(os.ProcAttr) + // Pass on stdin, stdout, stderr + files := []*os.File{os.Stdin, os.Stdout, os.Stderr} + attr.Files = files + cmd := []string{binary} + cmd = append(cmd, []string{"-listen", listenSocket, "-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...) + // Add the ssh port + cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...) + if logrus.GetLevel() == logrus.DebugLevel { + cmd = append(cmd, "--debug") + fmt.Println(cmd) + } + _, err = os.StartProcess(cmd[0], cmd, attr) + return err +} + +func (v *MachineVM) getSocketandPid() (string, string, error) { + rtPath, err := getRuntimeDir() + if err != nil { + return "", "", err + } + if !rootless.IsRootless() { + rtPath = "/run" + } + socketDir := filepath.Join(rtPath, "podman") + pidFile := filepath.Join(socketDir, fmt.Sprintf("%s.pid", v.Name)) + qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name)) + return qemuSocket, pidFile, nil +} diff --git a/pkg/machine/qemu/options_darwin.go b/pkg/machine/qemu/options_darwin.go index 46ccf24cb..440937131 100644 --- a/pkg/machine/qemu/options_darwin.go +++ b/pkg/machine/qemu/options_darwin.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" ) -func getSocketDir() (string, error) { +func getRuntimeDir() (string, error) { tmpDir, ok := os.LookupEnv("TMPDIR") if !ok { return "", errors.New("unable to resolve TMPDIR") diff --git a/pkg/machine/qemu/options_darwin_amd64.go b/pkg/machine/qemu/options_darwin_amd64.go index 69f7982b2..ee1036291 100644 --- a/pkg/machine/qemu/options_darwin_amd64.go +++ b/pkg/machine/qemu/options_darwin_amd64.go @@ -5,7 +5,7 @@ var ( ) func (v *MachineVM) addArchOptions() []string { - opts := []string{"-cpu", "host"} + opts := []string{"-machine", "q35,accel=hvf:tcg"} return opts } diff --git a/pkg/machine/qemu/options_linux.go b/pkg/machine/qemu/options_linux.go index 0a2e40d8f..c73a68cc6 100644 --- a/pkg/machine/qemu/options_linux.go +++ b/pkg/machine/qemu/options_linux.go @@ -1,7 +1,13 @@ package qemu -import "github.com/containers/podman/v3/pkg/util" +import ( + "github.com/containers/podman/v3/pkg/rootless" + "github.com/containers/podman/v3/pkg/util" +) -func getSocketDir() (string, error) { +func getRuntimeDir() (string, error) { + if !rootless.IsRootless() { + return "/run", nil + } return util.GetRuntimeDir() } diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 918b9a7e6..0d1d6e93e 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -311,10 +311,10 @@ static void __attribute__((constructor)) init() do_socket_activation = true; saved_systemd_listen_pid = strdup(listen_pid); saved_systemd_listen_fds = strdup(listen_fds); - saved_systemd_listen_fdnames = strdup(listen_fdnames); + if (listen_fdnames != NULL) + saved_systemd_listen_fdnames = strdup(listen_fdnames); if (saved_systemd_listen_pid == NULL - || saved_systemd_listen_fds == NULL - || saved_systemd_listen_fdnames == NULL) + || saved_systemd_listen_fds == NULL) { fprintf (stderr, "save socket listen environments error: %s\n", strerror (errno)); _exit (EXIT_FAILURE); @@ -700,7 +700,9 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) sprintf (s, "%d", getpid()); setenv ("LISTEN_PID", s, true); setenv ("LISTEN_FDS", saved_systemd_listen_fds, true); - setenv ("LISTEN_FDNAMES", saved_systemd_listen_fdnames, true); + // Setting fdnames is optional for systemd_socket_activation + if (saved_systemd_listen_fdnames != NULL) + setenv ("LISTEN_FDNAMES", saved_systemd_listen_fdnames, true); } setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); @@ -896,7 +898,9 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re sprintf (s, "%d", getpid()); setenv ("LISTEN_PID", s, true); setenv ("LISTEN_FDS", saved_systemd_listen_fds, true); - setenv ("LISTEN_FDNAMES", saved_systemd_listen_fdnames, true); + // Setting fdnames is optional for systemd_socket_activation + if (saved_systemd_listen_fdnames != NULL) + setenv ("LISTEN_FDNAMES", saved_systemd_listen_fdnames, true); } setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index 0f667e2f4..1ee070888 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -36,7 +36,7 @@ Description=Podman {{{{.ServiceName}}}}.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor={{{{.GraphRoot}}}} {{{{.RunRoot}}}} +RequiresMountsFor={{{{.RunRoot}}}} ` // filterPodFlags removes --pod, --pod-id-file and --infra-conmon-pidfile from the specified command. diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index eb1fb67ff..72f321347 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -152,14 +152,14 @@ func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSyste return nil, errors.Errorf("could not determine storage store for container") } - graphRoot := store.GraphRoot() - if graphRoot == "" { - return nil, errors.Errorf("could not lookup container's graphroot: got empty string") - } - - runRoot := store.RunRoot() - if runRoot == "" { - return nil, errors.Errorf("could not lookup container's runroot: got empty string") + var runRoot string + if options.New { + runRoot = "%t/containers" + } else { + runRoot = store.RunRoot() + if runRoot == "" { + return nil, errors.Errorf("could not lookup container's runroot: got empty string") + } } envs := config.Spec.Process.Env @@ -172,7 +172,6 @@ func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSyste StopTimeout: timeout, GenerateTimestamp: true, CreateCommand: createCommand, - GraphRoot: graphRoot, RunRoot: runRoot, containerEnv: envs, } diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 75b08526b..b1070fa52 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -48,7 +48,7 @@ Description=Podman container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -74,7 +74,7 @@ Description=Podman container-foobar.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -98,7 +98,7 @@ Description=Podman container-foobar.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage BindsTo=a.service b.service c.service pod.service After=a.service b.service c.service pod.service @@ -124,7 +124,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -149,7 +149,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -174,7 +174,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -199,7 +199,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -224,7 +224,7 @@ Description=Podman container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -250,7 +250,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -279,7 +279,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -304,7 +304,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -329,7 +329,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -354,7 +354,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -379,7 +379,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -404,7 +404,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -429,7 +429,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n @@ -454,7 +454,7 @@ Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage [Service] Environment=PODMAN_SYSTEMD_UNIT=%n diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index 0e4d92c50..a11e1e11e 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -47,7 +47,7 @@ Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service Before=container-1.service container-2.service @@ -75,7 +75,7 @@ Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service Before=container-1.service container-2.service @@ -103,7 +103,7 @@ Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service Before=container-1.service container-2.service @@ -131,7 +131,7 @@ Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service Before=container-1.service container-2.service @@ -159,7 +159,7 @@ Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) Wants=network.target After=network-online.target -RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage +RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service Before=container-1.service container-2.service |