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 <jhonce@redhat.com>
This commit is contained in:
Jhon Honce
2021-08-02 14:09:55 -07:00
committed by Matthew Heon
parent 4d244357a2
commit dda3e5b4e4
33 changed files with 360 additions and 35 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
@ -228,6 +244,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
@ -242,6 +259,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}
@ -276,6 +294,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 {
@ -308,6 +327,8 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) erro
if err != nil {
return err
}
defer response.Body.Close()
return response.Process(nil)
}
@ -331,6 +352,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)
}
@ -350,6 +373,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
}
@ -371,6 +396,8 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) error {
if err != nil {
return err
}
defer response.Body.Close()
return response.Process(nil)
}
@ -390,6 +417,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
@ -413,6 +442,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)
}
@ -432,5 +463,7 @@ func ShouldRestart(ctx context.Context, nameOrID string, options *ShouldRestartO
if err != nil {
return false, err
}
defer response.Body.Close()
return response.IsSuccess(), nil
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}

View File

@ -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 {

View File

@ -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}
}

View File

@ -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)
//}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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())
}
}()

View File

@ -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() {

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -1315,10 +1315,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"})
@ -1329,14 +1329,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() {