From 1dc6d14735eef1e51368103aefba3d7c704dcfe3 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Mon, 2 Aug 2021 14:09:55 -0700 Subject: [PATCH] Fix file descriptor leaks and add test * Add response.Body.Close() where needed to release HTTP connections to API server. * Add tests to ensure no general leaks occur. 100% coverage would be required to ensure no leaks on any call. * Update code comments to be godoc correct Signed-off-by: Jhon Honce --- cmd/podman/generate/kube.go | 5 ++ pkg/bindings/connection.go | 7 +- pkg/bindings/containers/archive.go | 10 ++- pkg/bindings/containers/attach.go | 9 +- pkg/bindings/containers/checkpoint.go | 4 + pkg/bindings/containers/commit.go | 2 + pkg/bindings/containers/containers.go | 33 +++++++ pkg/bindings/containers/create.go | 2 + pkg/bindings/containers/diff.go | 2 + pkg/bindings/containers/exec.go | 3 + pkg/bindings/containers/healthcheck.go | 2 + pkg/bindings/containers/logs.go | 1 + pkg/bindings/containers/mount.go | 6 ++ pkg/bindings/containers/rename.go | 2 + pkg/bindings/errors.go | 2 + pkg/bindings/generate/generate.go | 5 ++ pkg/bindings/images/diff.go | 2 + pkg/bindings/images/images.go | 32 +++++-- pkg/bindings/images/rm.go | 2 + pkg/bindings/manifests/manifests.go | 21 ++++- pkg/bindings/network/network.go | 16 ++++ pkg/bindings/play/play.go | 2 + pkg/bindings/pods/pods.go | 27 ++++++ pkg/bindings/secrets/secrets.go | 8 ++ pkg/bindings/system/info.go | 10 +-- pkg/bindings/system/system.go | 7 ++ pkg/bindings/test/attach_test.go | 5 +- pkg/bindings/test/common_test.go | 21 +++-- pkg/bindings/test/resource_test.go | 116 +++++++++++++++++++++++++ pkg/bindings/volumes/volumes.go | 12 +++ pkg/domain/entities/generate.go | 2 + pkg/domain/infra/tunnel/generate.go | 3 + test/e2e/run_test.go | 14 +-- 33 files changed, 360 insertions(+), 35 deletions(-) create mode 100644 pkg/bindings/test/resource_test.go diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go index b4c9f91461..60b8f0af02 100644 --- a/cmd/podman/generate/kube.go +++ b/cmd/podman/generate/kube.go @@ -2,6 +2,7 @@ package pods import ( "fmt" + "io" "io/ioutil" "os" @@ -61,6 +62,10 @@ func kube(cmd *cobra.Command, args []string) error { if err != nil { return err } + if r, ok := report.Reader.(io.ReadCloser); ok { + defer r.Close() + } + if cmd.Flags().Changed("filename") { if _, err := os.Stat(kubeFile); err == nil { return errors.Errorf("cannot write to %q; file exists", kubeFile) diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index cd118cbb24..4127ad2f00 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -56,7 +56,7 @@ func NewConnection(ctx context.Context, uri string) (context.Context, error) { return NewConnectionWithIdentity(ctx, uri, "") } -// NewConnection takes a URI as a string and returns a context with the +// NewConnectionWithIdentity takes a URI as a string and returns a context with the // Connection embedded as a value. This context needs to be passed to each // endpoint to work correctly. // @@ -149,6 +149,7 @@ func pingNewConnection(ctx context.Context) error { if err != nil { return err } + defer response.Body.Close() if response.StatusCode == http.StatusOK { versionHdr := response.Header.Get("Libpod-API-Version") @@ -338,7 +339,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, req.Header.Set(key, val) } // Give the Do three chances in the case of a comm/service hiccup - for i := 0; i < 3; i++ { + for i := 1; i <= 3; i++ { response, err = c.Client.Do(req) // nolint if err == nil { break @@ -358,7 +359,7 @@ func FiltersToString(filters map[string][]string) (string, error) { return jsoniter.MarshalToString(lowerCaseKeys) } -// IsInformation returns true if the response code is 1xx +// IsInformational returns true if the response code is 1xx func (h *APIResponse) IsInformational() bool { return h.Response.StatusCode/100 == 1 } diff --git a/pkg/bindings/containers/archive.go b/pkg/bindings/containers/archive.go index 52b73662b5..876f5340bd 100644 --- a/pkg/bindings/containers/archive.go +++ b/pkg/bindings/containers/archive.go @@ -27,6 +27,7 @@ func Stat(ctx context.Context, nameOrID string, path string) (*entities.Containe if err != nil { return nil, err } + defer response.Body.Close() var finalErr error if response.StatusCode == http.StatusNotFound { @@ -53,7 +54,9 @@ func CopyFromArchive(ctx context.Context, nameOrID string, path string, reader i return CopyFromArchiveWithOptions(ctx, nameOrID, path, reader, nil) } -// CopyFromArchiveWithOptions FIXME: remove this function and make CopyFromArchive accept the option as the last parameter in podman 4.0 +// CopyFromArchiveWithOptions copy files into container +// +// FIXME: remove this function and make CopyFromArchive accept the option as the last parameter in podman 4.0 func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path string, reader io.Reader, options *CopyOptions) (entities.ContainerCopyFunc, error) { conn, err := bindings.GetClient(ctx) if err != nil { @@ -72,6 +75,7 @@ func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path strin if err != nil { return err } + if response.StatusCode != http.StatusOK { return errors.New(response.Status) } @@ -79,6 +83,7 @@ func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path strin }, nil } +// CopyToArchive copy files from container func CopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) { conn, err := bindings.GetClient(ctx) if err != nil { @@ -91,11 +96,14 @@ func CopyToArchive(ctx context.Context, nameOrID string, path string, writer io. if err != nil { return nil, err } + if response.StatusCode != http.StatusOK { + defer response.Body.Close() return nil, response.Process(nil) } return func() error { + defer response.Body.Close() _, err := io.Copy(writer, response.Body) return err }, nil diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index 01c14d350a..6efbcb57b9 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -134,7 +134,9 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri if err != nil { return err } + if !(response.IsSuccess() || response.IsInformational()) { + defer response.Body.Close() return response.Process(nil) } @@ -207,7 +209,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri } } } else { - logrus.Debugf("Copying standard streams of container in non-terminal mode") + logrus.Debugf("Copying standard streams of container %q in non-terminal mode", ctnr.ID) for { // Read multiplexed channels and write to appropriate stream fd, l, err := DemuxHeader(socket, buffer) @@ -324,6 +326,8 @@ func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) er if err != nil { return err } + defer rsp.Body.Close() + return rsp.Process(nil) } @@ -407,6 +411,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar if err != nil { return err } + defer resp.Body.Close() respStruct := new(define.InspectExecSession) if err := resp.Process(respStruct); err != nil { @@ -477,6 +482,8 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar if err != nil { return err } + defer response.Body.Close() + if !(response.IsSuccess() || response.IsInformational()) { return response.Process(nil) } diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go index 440bf707d6..7f7080f13b 100644 --- a/pkg/bindings/containers/checkpoint.go +++ b/pkg/bindings/containers/checkpoint.go @@ -27,6 +27,8 @@ func Checkpoint(ctx context.Context, nameOrID string, options *CheckpointOptions if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -54,5 +56,7 @@ func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } diff --git a/pkg/bindings/containers/commit.go b/pkg/bindings/containers/commit.go index 8ae61322e0..a4adebb1f3 100644 --- a/pkg/bindings/containers/commit.go +++ b/pkg/bindings/containers/commit.go @@ -28,5 +28,7 @@ func Commit(ctx context.Context, nameOrID string, options *CommitOptions) (handl if err != nil { return id, err } + defer response.Body.Close() + return id, response.Process(&id) } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index bc7b0c8c9d..aafb83f65f 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -42,6 +42,8 @@ func List(ctx context.Context, options *ListOptions) ([]entities.ListContainer, if err != nil { return containers, err } + defer response.Body.Close() + return containers, response.Process(&containers) } @@ -66,6 +68,8 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } @@ -90,6 +94,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -113,6 +119,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*de if err != nil { return nil, err } + defer response.Body.Close() + inspect := define.InspectContainerData{} return &inspect, response.Process(&inspect) } @@ -136,6 +144,8 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -154,6 +164,8 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -176,6 +188,8 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) erro if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -199,6 +213,8 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -231,6 +247,7 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha go func() { defer close(statsChan) + defer response.Body.Close() dec := json.NewDecoder(response.Body) doStream := true @@ -245,6 +262,7 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha default: // fall through and do some work } + var report entities.ContainerStatsReport if err := dec.Decode(&report); err != nil { report = entities.ContainerStatsReport{Error: err} @@ -279,6 +297,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e if err != nil { return nil, err } + defer response.Body.Close() body := handlers.ContainerTopOKBody{} if err = response.Process(&body); err != nil { @@ -311,6 +330,8 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) erro if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -334,6 +355,8 @@ func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, er if err != nil { return exitCode, err } + defer response.Body.Close() + return exitCode, response.Process(&exitCode) } @@ -353,6 +376,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -374,6 +399,8 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -393,6 +420,8 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, options *ExportOp if err != nil { return err } + defer response.Body.Close() + if response.StatusCode/100 == 2 { _, err = io.Copy(w, response.Body) return err @@ -416,6 +445,8 @@ func ContainerInit(ctx context.Context, nameOrID string, options *InitOptions) e if err != nil { return err } + defer response.Body.Close() + if response.StatusCode == http.StatusNotModified { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has already been created in runtime", nameOrID) } @@ -435,5 +466,7 @@ func ShouldRestart(ctx context.Context, nameOrID string, options *ShouldRestartO if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go index 3efa9643de..c0b9538a6c 100644 --- a/pkg/bindings/containers/create.go +++ b/pkg/bindings/containers/create.go @@ -30,5 +30,7 @@ func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator, options *Crea if err != nil { return ccr, err } + defer response.Body.Close() + return ccr, response.Process(&ccr) } diff --git a/pkg/bindings/containers/diff.go b/pkg/bindings/containers/diff.go index 7d20ae5307..e4ec49809a 100644 --- a/pkg/bindings/containers/diff.go +++ b/pkg/bindings/containers/diff.go @@ -26,6 +26,8 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive if err != nil { return nil, err } + defer response.Body.Close() + var changes []archive.Change return changes, response.Process(&changes) } diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go index 5ae6d1d712..12b31aba35 100644 --- a/pkg/bindings/containers/exec.go +++ b/pkg/bindings/containers/exec.go @@ -39,6 +39,7 @@ func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreat if err != nil { return "", err } + defer resp.Body.Close() respStruct := new(handlers.ExecCreateResponse) if err := resp.Process(respStruct); err != nil { @@ -66,6 +67,7 @@ func ExecInspect(ctx context.Context, sessionID string, options *ExecInspectOpti if err != nil { return nil, err } + defer resp.Body.Close() respStruct := new(define.InspectExecSession) if err := resp.Process(respStruct); err != nil { @@ -103,6 +105,7 @@ func ExecStart(ctx context.Context, sessionID string, options *ExecStartOptions) if err != nil { return err } + defer resp.Body.Close() return resp.Process(nil) } diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go index d6b7216152..0e65a5a464 100644 --- a/pkg/bindings/containers/healthcheck.go +++ b/pkg/bindings/containers/healthcheck.go @@ -26,5 +26,7 @@ func RunHealthCheck(ctx context.Context, nameOrID string, options *HealthCheckOp if err != nil { return nil, err } + defer response.Body.Close() + return &status, response.Process(&status) } diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go index a3100c697e..67db944872 100644 --- a/pkg/bindings/containers/logs.go +++ b/pkg/bindings/containers/logs.go @@ -33,6 +33,7 @@ func Logs(ctx context.Context, nameOrID string, options *LogOptions, stdoutChan, if err != nil { return err } + defer response.Body.Close() buffer := make([]byte, 1024) for { diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go index bb5c3bd67a..c07998fd39 100644 --- a/pkg/bindings/containers/mount.go +++ b/pkg/bindings/containers/mount.go @@ -25,6 +25,8 @@ func Mount(ctx context.Context, nameOrID string, options *MountOptions) (string, if err != nil { return path, err } + defer response.Body.Close() + return path, response.Process(&path) } @@ -43,6 +45,8 @@ func Unmount(ctx context.Context, nameOrID string, options *UnmountOptions) erro if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -61,5 +65,7 @@ func GetMountedContainerPaths(ctx context.Context, options *MountedContainerPath if err != nil { return mounts, err } + defer response.Body.Close() + return mounts, response.Process(&mounts) } diff --git a/pkg/bindings/containers/rename.go b/pkg/bindings/containers/rename.go index 60d7fda73a..172d7838a3 100644 --- a/pkg/bindings/containers/rename.go +++ b/pkg/bindings/containers/rename.go @@ -24,5 +24,7 @@ func Rename(ctx context.Context, nameOrID string, options *RenameOptions) error if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } diff --git a/pkg/bindings/errors.go b/pkg/bindings/errors.go index 3339062a58..9c311d9120 100644 --- a/pkg/bindings/errors.go +++ b/pkg/bindings/errors.go @@ -20,6 +20,8 @@ func handleError(data []byte) error { return e } +// Process drains the response body, and processes the HTTP status code +// Note: Closing the response.Body is left to the caller func (h APIResponse) Process(unmarshalInto interface{}) error { data, err := ioutil.ReadAll(h.Response.Body) if err != nil { diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go index 7c904a6a8a..7429565155 100644 --- a/pkg/bindings/generate/generate.go +++ b/pkg/bindings/generate/generate.go @@ -26,10 +26,15 @@ func Systemd(ctx context.Context, nameOrID string, options *SystemdOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + report := &entities.GenerateSystemdReport{} return report, response.Process(&report.Units) } +// Kube generate Kubernetes YAML (v1 specification) +// +// Note: Caller is responsible for closing returned reader func Kube(ctx context.Context, nameOrIDs []string, options *KubeOptions) (*entities.GenerateKubeReport, error) { if options == nil { options = new(KubeOptions) diff --git a/pkg/bindings/images/diff.go b/pkg/bindings/images/diff.go index 79b0df8c99..671b73089a 100644 --- a/pkg/bindings/images/diff.go +++ b/pkg/bindings/images/diff.go @@ -23,6 +23,8 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive if err != nil { return nil, err } + defer response.Body.Close() + var changes []archive.Change return changes, response.Process(&changes) } diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 8680d6baa9..959481e0dc 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -27,6 +27,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -49,6 +51,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ImageSummary, if err != nil { return imageSummary, err } + defer response.Body.Close() + return imageSummary, response.Process(&imageSummary) } @@ -71,6 +75,8 @@ func GetImage(ctx context.Context, nameOrID string, options *GetOptions) (*entit if err != nil { return &inspectedData, err } + defer response.Body.Close() + return &inspectedData, response.Process(&inspectedData) } @@ -92,6 +98,8 @@ func Tree(ctx context.Context, nameOrID string, options *TreeOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -110,6 +118,8 @@ func History(ctx context.Context, nameOrID string, options *HistoryOptions) ([]* if err != nil { return history, err } + defer response.Body.Close() + return history, response.Process(&history) } @@ -123,6 +133,8 @@ func Load(ctx context.Context, r io.Reader) (*entities.ImageLoadReport, error) { if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -147,6 +159,7 @@ func Export(ctx context.Context, nameOrIDs []string, w io.Writer, options *Expor if err != nil { return err } + defer response.Body.Close() if response.StatusCode/100 == 2 || response.StatusCode/100 == 3 { _, err = io.Copy(w, response.Body) @@ -176,8 +189,9 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return deleted, err } - err = response.Process(&deleted) - return deleted, err + defer response.Body.Close() + + return deleted, response.Process(&deleted) } // Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required. @@ -197,6 +211,8 @@ func Tag(ctx context.Context, nameOrID, tag, repo string, options *TagOptions) e if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -217,10 +233,12 @@ func Untag(ctx context.Context, nameOrID, tag, repo string, options *UntagOption if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } -// Imports adds the given image to the local image store. This can be done by file and the given reader +// Import adds the given image to the local image store. This can be done by file and the given reader // or via the url parameter. Additional metadata can be associated with the image by using the changes and // message parameters. The image can also be tagged given a reference. One of url OR r must be provided. func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities.ImageImportReport, error) { @@ -243,6 +261,8 @@ func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -269,8 +289,8 @@ func Push(ctx context.Context, source string, destination string, options *PushO if err != nil { return err } - //SkipTLSVerify is special. We need to delete the param added by - //toparams and change the key and flip the bool + // SkipTLSVerify is special. We need to delete the param added by + // toparams and change the key and flip the bool if options.SkipTLSVerify != nil { params.Del("SkipTLSVerify") params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) @@ -282,6 +302,7 @@ func Push(ctx context.Context, source string, destination string, options *PushO if err != nil { return err } + defer response.Body.Close() return response.Process(err) } @@ -317,6 +338,7 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie if err != nil { return nil, err } + defer response.Body.Close() results := []entities.ImageSearchReport{} if err := response.Process(&results); err != nil { diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go index e45e583f4c..461eb77296 100644 --- a/pkg/bindings/images/rm.go +++ b/pkg/bindings/images/rm.go @@ -36,6 +36,8 @@ func Remove(ctx context.Context, images []string, options *RemoveOptions) (*enti if err != nil { return nil, []error{err} } + defer response.Body.Close() + if err := response.Process(&report); err != nil { return nil, []error{err} } diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index 268ce3b19f..6aa4961f1d 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -46,10 +46,12 @@ func Create(ctx context.Context, names, images []string, options *CreateOptions) if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, response.Process(&idr) } -// Exists returns true if a given maifest list exists +// Exists returns true if a given manifest list exists func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, error) { conn, err := bindings.GetClient(ctx) if err != nil { @@ -59,6 +61,8 @@ func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, err if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -77,6 +81,8 @@ func Inspect(ctx context.Context, name string, options *InspectOptions) (*manife if err != nil { return nil, err } + defer response.Body.Close() + return &list, response.Process(&list) } @@ -100,6 +106,8 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error) if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, response.Process(&idr) } @@ -121,6 +129,8 @@ func Remove(ctx context.Context, name, digest string, options *RemoveOptions) (s if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, response.Process(&idr) } @@ -145,18 +155,20 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt if err != nil { return "", err } - //SkipTLSVerify is special. We need to delete the param added by - //toparams and change the key and flip the bool + // SkipTLSVerify is special. We need to delete the param added by + // toparams and change the key and flip the bool if options.SkipTLSVerify != nil { params.Del("SkipTLSVerify") params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) } params.Set("image", name) params.Set("destination", destination) - _, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name) + response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name) if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, err } @@ -179,5 +191,6 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt // if err != nil { // return "", err // } +// defer response.Body.Close() // return idr.ID, response.Process(&idr) //} diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 17451c273d..59207aa8d0 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -34,6 +34,8 @@ func Create(ctx context.Context, options *CreateOptions) (*entities.NetworkCreat if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -53,6 +55,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) ([]e if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports[0]) } @@ -76,6 +80,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) ([]*en if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } @@ -99,6 +105,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.NetworkListRep if err != nil { return netList, err } + defer response.Body.Close() + return netList, response.Process(&netList) } @@ -133,6 +141,8 @@ func Disconnect(ctx context.Context, networkName string, ContainerNameOrID strin if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -166,6 +176,8 @@ func Connect(ctx context.Context, networkName string, ContainerNameOrID string, if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -179,6 +191,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -203,5 +217,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPrune if err != nil { return nil, err } + defer response.Body.Close() + return prunedNetworks, response.Process(&prunedNetworks) } diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go index 4b735c8217..8451cd5335 100644 --- a/pkg/bindings/play/play.go +++ b/pkg/bindings/play/play.go @@ -48,6 +48,8 @@ func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.Pla if err != nil { return nil, err } + defer response.Body.Close() + if err := response.Process(&report); err != nil { return nil, err } diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index eb7b273cf4..9d3ff322ec 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -34,6 +34,8 @@ func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator, options if err != nil { return nil, err } + defer response.Body.Close() + return &pcr, response.Process(&pcr) } @@ -47,6 +49,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -67,6 +71,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -91,6 +97,8 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -109,6 +117,8 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) (*entiti if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -128,6 +138,8 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.PodPruneRepo if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } @@ -152,6 +164,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ListPodsReport if err != nil { return podsReports, err } + defer response.Body.Close() + return podsReports, response.Process(&podsReports) } @@ -170,6 +184,8 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -192,6 +208,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*enti if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -210,6 +228,8 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) (*entiti if err != nil { return nil, err } + defer response.Body.Close() + if response.StatusCode == http.StatusNotModified { report.Id = nameOrID return &report, nil @@ -236,6 +256,8 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + if response.StatusCode == http.StatusNotModified { report.Id = nameOrID return &report, nil @@ -261,6 +283,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e if err != nil { return nil, err } + defer response.Body.Close() body := handlers.PodTopOKBody{} if err = response.Process(&body); err != nil { @@ -293,6 +316,8 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -318,5 +343,7 @@ func Stats(ctx context.Context, namesOrIDs []string, options *StatsOptions) ([]* if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } diff --git a/pkg/bindings/secrets/secrets.go b/pkg/bindings/secrets/secrets.go index 091d38e56c..b741d3e5c4 100644 --- a/pkg/bindings/secrets/secrets.go +++ b/pkg/bindings/secrets/secrets.go @@ -22,6 +22,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.SecretInfoRepo if err != nil { return secrs, err } + defer response.Body.Close() + return secrs, response.Process(&secrs) } @@ -38,6 +40,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return inspect, err } + defer response.Body.Close() + return inspect, response.Process(&inspect) } @@ -52,6 +56,8 @@ func Remove(ctx context.Context, nameOrID string) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -74,5 +80,7 @@ func Create(ctx context.Context, reader io.Reader, options *CreateOptions) (*ent if err != nil { return nil, err } + defer response.Body.Close() + return create, response.Process(&create) } diff --git a/pkg/bindings/system/info.go b/pkg/bindings/system/info.go index 244f9643e2..8a307a4ca9 100644 --- a/pkg/bindings/system/info.go +++ b/pkg/bindings/system/info.go @@ -9,12 +9,7 @@ import ( ) // Info returns information about the libpod environment and its stores -func Info(ctx context.Context, options *InfoOptions) (*define.Info, error) { - if options == nil { - options = new(InfoOptions) - } - _ = options - info := define.Info{} +func Info(ctx context.Context, _ *InfoOptions) (*define.Info, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -23,5 +18,8 @@ func Info(ctx context.Context, options *InfoOptions) (*define.Info, error) { if err != nil { return nil, err } + defer response.Body.Close() + + info := define.Info{} return &info, response.Process(&info) } diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go index 310bcef15a..719cde52e3 100644 --- a/pkg/bindings/system/system.go +++ b/pkg/bindings/system/system.go @@ -31,6 +31,8 @@ func Events(ctx context.Context, eventChan chan entities.Event, cancelChan chan if err != nil { return err } + defer response.Body.Close() + if cancelChan != nil { go func() { <-cancelChan @@ -75,6 +77,8 @@ func Prune(ctx context.Context, options *PruneOptions) (*entities.SystemPruneRep if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -101,6 +105,7 @@ func Version(ctx context.Context, options *VersionOptions) (*entities.SystemVers if err != nil { return nil, err } + defer response.Body.Close() if err = response.Process(&component); err != nil { return nil, err @@ -141,5 +146,7 @@ func DiskUsage(ctx context.Context, options *DiskOptions) (*entities.SystemDfRep if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go index fbdf18d44d..5c3ec48e4f 100644 --- a/pkg/bindings/test/attach_test.go +++ b/pkg/bindings/test/attach_test.go @@ -81,10 +81,9 @@ var _ = Describe("Podman containers attach", func() { tickTock := time.NewTimer(2 * time.Second) go func() { <-tickTock.C - timeout := uint(5) - err := containers.Stop(bt.conn, ctnr.ID, new(containers.StopOptions).WithTimeout(timeout)) + err := containers.Stop(bt.conn, ctnr.ID, new(containers.StopOptions).WithTimeout(uint(5))) if err != nil { - GinkgoWriter.Write([]byte(err.Error())) + fmt.Fprint(GinkgoWriter, err.Error()) } }() diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index 9bac4b620b..91ebe21fca 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "strings" + "time" "github.com/containers/podman/v3/libpod/define" . "github.com/containers/podman/v3/pkg/bindings" @@ -150,11 +151,21 @@ func createTempDirInTempDir() (string, error) { } func (b *bindingTest) startAPIService() *gexec.Session { - var ( - cmd []string - ) - cmd = append(cmd, "--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock) - return b.runPodman(cmd) + cmd := []string{"--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock} + session := b.runPodman(cmd) + + sock := strings.TrimPrefix(b.sock, "unix://") + for i := 0; i < 10; i++ { + if _, err := os.Stat(sock); err != nil { + if !os.IsNotExist(err) { + break + } + time.Sleep(time.Second) + continue + } + break + } + return session } func (b *bindingTest) cleanup() { diff --git a/pkg/bindings/test/resource_test.go b/pkg/bindings/test/resource_test.go new file mode 100644 index 0000000000..b12d1ccd68 --- /dev/null +++ b/pkg/bindings/test/resource_test.go @@ -0,0 +1,116 @@ +package test_bindings + +import ( + "context" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "reflect" + "strconv" + "syscall" + + "github.com/containers/podman/v3/pkg/bindings" + "github.com/containers/podman/v3/pkg/bindings/containers" + "github.com/containers/podman/v3/pkg/bindings/images" + "github.com/containers/podman/v3/pkg/bindings/pods" + "github.com/containers/podman/v3/pkg/bindings/system" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Verify Podman resources", func() { + var ( + bt *bindingTest + s *Session + ) + + BeforeEach(func() { + bt = newBindingTest() + s = bt.startAPIService() + err := bt.NewConnection() + Expect(err).ShouldNot(HaveOccurred()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("no leaked connections", func() { + conn, err := bindings.NewConnection(context.Background(), bt.sock) + Expect(err).ShouldNot(HaveOccurred()) + + // Record details on open file descriptors before using API + buffer := lsof() + + // Record open fd from /proc + start, err := readProc() + Expect(err).ShouldNot(HaveOccurred()) + + // Run some operations + _, err = system.Info(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + _, err = images.List(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + _, err = containers.List(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + _, err = pods.List(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + + podman, _ := bindings.GetClient(conn) + podman.Client.CloseIdleConnections() + + // Record open fd from /proc + finished, err := readProc() + Expect(err).ShouldNot(HaveOccurred()) + if !reflect.DeepEqual(finished, start) { + fmt.Fprintf(GinkgoWriter, "Open FDs:\nlsof Before:\n%s\n", buffer) + + // Record details on open file descriptors after using API + buffer := lsof() + fmt.Fprintf(GinkgoWriter, "lsof After:\n%s\n", buffer) + + // We know test has failed. Easier to let ginkgo format output. + Expect(finished).Should(Equal(start)) + } + }) +}) + +func lsof() string { + lsof := exec.Command("lsof", "+E", "-p", strconv.Itoa(os.Getpid())) + buffer, err := lsof.Output() + Expect(err).ShouldNot(HaveOccurred()) + return string(buffer) +} + +func readProc() ([]string, error) { + syscall.Sync() + + names := make([]string, 0) + err := filepath.WalkDir(fmt.Sprintf("/proc/%d/fd", os.Getpid()), + func(path string, d fs.DirEntry, err error) error { + name := path + " -> " + + switch { + case d.IsDir(): + return nil + case err != nil: + name += err.Error() + case d.Type()&fs.ModeSymlink != 0: + n, err := os.Readlink(path) + if err != nil && !os.IsNotExist(err) { + return err + } + if n == "" { + n = d.Type().String() + } + name += n + } + names = append(names, name) + return nil + }) + return names, err +} diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index fb58a1d1f5..56cf13ade9 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -33,6 +33,8 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions, options *C if err != nil { return nil, err } + defer response.Body.Close() + return &v, response.Process(&v) } @@ -53,6 +55,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return &inspect, err } + defer response.Body.Close() + return &inspect, response.Process(&inspect) } @@ -74,6 +78,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.VolumeListRepo if err != nil { return vols, err } + defer response.Body.Close() + return vols, response.Process(&vols) } @@ -94,6 +100,8 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return nil, err } + defer response.Body.Close() + return pruned, response.Process(&pruned) } @@ -112,6 +120,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -125,5 +135,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index 3ec713edfe..8a437061f5 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -35,6 +35,8 @@ type GenerateKubeOptions struct { } // GenerateKubeReport +// +// FIXME: Podman4.0 should change io.Reader to io.ReaderCloser type GenerateKubeReport struct { // Reader - the io.Reader to reader the generated YAML file. Reader io.Reader diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index 0e768b30bc..3d3cd52be4 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -16,6 +16,9 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, return generate.Systemd(ic.ClientCtx, nameOrID, options) } +// GenerateKube Kubernetes YAML (v1 specification) for nameOrIDs +// +// Note: Caller is responsible for closing returned Reader func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, opts entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { options := new(generate.KubeOptions).WithService(opts.Service) return generate.Kube(ic.ClientCtx, nameOrIDs, options) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 6a2e2ed8d6..846da283dc 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1331,10 +1331,10 @@ USER mail`, BB) } curCgroupsBytes, err := ioutil.ReadFile("/proc/self/cgroup") - Expect(err).To(BeNil()) - var curCgroups string = string(curCgroupsBytes) + Expect(err).ShouldNot(HaveOccurred()) + var curCgroups = string(curCgroupsBytes) fmt.Printf("Output:\n%s\n", curCgroups) - Expect(curCgroups).To(Not(Equal(""))) + Expect(curCgroups).ToNot(Equal("")) ctrName := "testctr" container := podmanTest.Podman([]string{"run", "--name", ctrName, "-d", "--cgroups=disabled", ALPINE, "top"}) @@ -1345,14 +1345,14 @@ USER mail`, BB) inspectOut := podmanTest.InspectContainer(ctrName) Expect(len(inspectOut)).To(Equal(1)) pid := inspectOut[0].State.Pid - Expect(pid).To(Not(Equal(0))) + Expect(pid).ToNot(Equal(0)) Expect(inspectOut[0].HostConfig.CgroupParent).To(Equal("")) ctrCgroupsBytes, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid)) - Expect(err).To(BeNil()) - var ctrCgroups string = string(ctrCgroupsBytes) + Expect(err).ShouldNot(HaveOccurred()) + var ctrCgroups = string(ctrCgroupsBytes) fmt.Printf("Output\n:%s\n", ctrCgroups) - Expect(curCgroups).To(Equal(ctrCgroups)) + Expect(ctrCgroups).To(Equal(curCgroups)) }) It("podman run with cgroups=enabled makes cgroups", func() {