github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/bindings/containers/containers.go (about)

     1  package containers
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"github.com/hanks177/podman/v4/libpod/define"
    11  	"github.com/hanks177/podman/v4/pkg/api/handlers"
    12  	"github.com/hanks177/podman/v4/pkg/bindings"
    13  	"github.com/hanks177/podman/v4/pkg/domain/entities"
    14  	"github.com/hanks177/podman/v4/pkg/domain/entities/reports"
    15  	"github.com/pkg/errors"
    16  	"github.com/sirupsen/logrus"
    17  )
    18  
    19  var (
    20  	ErrLostSync = errors.New("lost synchronization with multiplexed stream")
    21  )
    22  
    23  // List obtains a list of containers in local storage.  All parameters to this method are optional.
    24  // The filters are used to determine which containers are listed. The last parameter indicates to only return
    25  // the most recent number of containers.  The pod and size booleans indicate that pod information and rootfs
    26  // size information should also be included.  Finally, the sync bool synchronizes the OCI runtime and
    27  // container state.
    28  func List(ctx context.Context, options *ListOptions) ([]entities.ListContainer, error) { // nolint:typecheck
    29  	if options == nil {
    30  		options = new(ListOptions)
    31  	}
    32  	conn, err := bindings.GetClient(ctx)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	var containers []entities.ListContainer
    37  	params, err := options.ToParams()
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/json", params, nil)
    42  	if err != nil {
    43  		return containers, err
    44  	}
    45  	defer response.Body.Close()
    46  
    47  	return containers, response.Process(&containers)
    48  }
    49  
    50  // Prune removes stopped and exited containers from local storage.  The optional filters can be
    51  // used for more granular selection of containers.  The main error returned indicates if there were runtime
    52  // errors like finding containers.  Errors specific to the removal of a container are in the PruneContainerResponse
    53  // structure.
    54  func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, error) {
    55  	if options == nil {
    56  		options = new(PruneOptions)
    57  	}
    58  	var reports []*reports.PruneReport
    59  	conn, err := bindings.GetClient(ctx)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	params, err := options.ToParams()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/prune", params, nil)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	defer response.Body.Close()
    72  
    73  	return reports, response.Process(&reports)
    74  }
    75  
    76  // Remove removes a container from local storage.  The force bool designates
    77  // that the container should be removed forcibly (example, even it is running).
    78  // The volumes bool dictates that a container's volumes should also be removed.
    79  // The All option indicates that all containers should be removed
    80  // The Ignore option indicates that if a container did not exist, ignore the error
    81  func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) ([]*reports.RmReport, error) {
    82  	if options == nil {
    83  		options = new(RemoveOptions)
    84  	}
    85  	var reports []*reports.RmReport
    86  	conn, err := bindings.GetClient(ctx)
    87  	if err != nil {
    88  		return reports, err
    89  	}
    90  	params, err := options.ToParams()
    91  	if err != nil {
    92  		return reports, err
    93  	}
    94  	response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID)
    95  	if err != nil {
    96  		return reports, err
    97  	}
    98  	defer response.Body.Close()
    99  
   100  	return reports, response.Process(&reports)
   101  }
   102  
   103  // Inspect returns low level information about a Container.  The nameOrID can be a container name
   104  // or a partial/full ID.  The size bool determines whether the size of the container's root filesystem
   105  // should be calculated.  Calculating the size of a container requires extra work from the filesystem and
   106  // is therefore slower.
   107  func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*define.InspectContainerData, error) {
   108  	if options == nil {
   109  		options = new(InspectOptions)
   110  	}
   111  	conn, err := bindings.GetClient(ctx)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	params, err := options.ToParams()
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	defer response.Body.Close()
   124  
   125  	inspect := define.InspectContainerData{}
   126  	return &inspect, response.Process(&inspect)
   127  }
   128  
   129  // Kill sends a given signal to a given container.  The signal should be the string
   130  // representation of a signal like 'SIGKILL'. The nameOrID can be a container name
   131  // or a partial/full ID
   132  func Kill(ctx context.Context, nameOrID string, options *KillOptions) error {
   133  	if options == nil {
   134  		options = new(KillOptions)
   135  	}
   136  	conn, err := bindings.GetClient(ctx)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	params, err := options.ToParams()
   141  	if err != nil {
   142  		return err
   143  	}
   144  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	defer response.Body.Close()
   149  
   150  	return response.Process(nil)
   151  }
   152  
   153  // Pause pauses a given container.  The nameOrID can be a container name
   154  // or a partial/full ID.
   155  func Pause(ctx context.Context, nameOrID string, options *PauseOptions) error {
   156  	if options == nil {
   157  		options = new(PauseOptions)
   158  	}
   159  	_ = options
   160  	conn, err := bindings.GetClient(ctx)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	defer response.Body.Close()
   169  
   170  	return response.Process(nil)
   171  }
   172  
   173  // Restart restarts a running container. The nameOrID can be a container name
   174  // or a partial/full ID.  The optional timeout specifies the number of seconds to wait
   175  // for the running container to stop before killing it.
   176  func Restart(ctx context.Context, nameOrID string, options *RestartOptions) error {
   177  	if options == nil {
   178  		options = new(RestartOptions)
   179  	}
   180  	conn, err := bindings.GetClient(ctx)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	params, err := options.ToParams()
   185  	if err != nil {
   186  		return err
   187  	}
   188  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	defer response.Body.Close()
   193  
   194  	return response.Process(nil)
   195  }
   196  
   197  // Start starts a non-running container.The nameOrID can be a container name
   198  // or a partial/full ID. The optional parameter for detach keys are to override the default
   199  // detach key sequence.
   200  func Start(ctx context.Context, nameOrID string, options *StartOptions) error {
   201  	if options == nil {
   202  		options = new(StartOptions)
   203  	}
   204  	logrus.Infof("Going to start container %q", nameOrID)
   205  	conn, err := bindings.GetClient(ctx)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	params, err := options.ToParams()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	defer response.Body.Close()
   218  
   219  	return response.Process(nil)
   220  }
   221  
   222  func Stats(ctx context.Context, containers []string, options *StatsOptions) (chan entities.ContainerStatsReport, error) {
   223  	if options == nil {
   224  		options = new(StatsOptions)
   225  	}
   226  	_ = options
   227  	conn, err := bindings.GetClient(ctx)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	params, err := options.ToParams()
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	for _, c := range containers {
   236  		params.Add("containers", c)
   237  	}
   238  
   239  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/stats", params, nil)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	if !response.IsSuccess() {
   244  		return nil, response.Process(nil)
   245  	}
   246  
   247  	statsChan := make(chan entities.ContainerStatsReport)
   248  
   249  	go func() {
   250  		defer close(statsChan)
   251  		defer response.Body.Close()
   252  
   253  		dec := json.NewDecoder(response.Body)
   254  		doStream := true
   255  		if options.Changed("Stream") {
   256  			doStream = options.GetStream()
   257  		}
   258  
   259  	streamLabel: // label to flatten the scope
   260  		select {
   261  		case <-response.Request.Context().Done():
   262  			return // lost connection - maybe the server quit
   263  		default:
   264  			// fall through and do some work
   265  		}
   266  
   267  		var report entities.ContainerStatsReport
   268  		if err := dec.Decode(&report); err != nil {
   269  			report = entities.ContainerStatsReport{Error: err}
   270  		}
   271  		statsChan <- report
   272  
   273  		if report.Error != nil || !doStream {
   274  			return
   275  		}
   276  		goto streamLabel
   277  	}()
   278  
   279  	return statsChan, nil
   280  }
   281  
   282  // Top gathers statistics about the running processes in a container. The nameOrID can be a container name
   283  // or a partial/full ID.  The descriptors allow for specifying which data to collect from the process.
   284  func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, error) {
   285  	if options == nil {
   286  		options = new(TopOptions)
   287  	}
   288  	conn, err := bindings.GetClient(ctx)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	params := url.Values{}
   293  	if options.Changed("Descriptors") {
   294  		psArgs := strings.Join(options.GetDescriptors(), ",")
   295  		params.Add("ps_args", psArgs)
   296  	}
   297  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  	defer response.Body.Close()
   302  
   303  	body := handlers.ContainerTopOKBody{}
   304  	if err = response.Process(&body); err != nil {
   305  		return nil, err
   306  	}
   307  
   308  	// handlers.ContainerTopOKBody{} returns a slice of slices where each cell in the top table is an item.
   309  	// In libpod land, we're just using a slice with cells being split by tabs, which allows for an idiomatic
   310  	// usage of the tabwriter.
   311  	topOutput := []string{strings.Join(body.Titles, "\t")}
   312  	for _, out := range body.Processes {
   313  		topOutput = append(topOutput, strings.Join(out, "\t"))
   314  	}
   315  
   316  	return topOutput, err
   317  }
   318  
   319  // Unpause resumes the given paused container.  The nameOrID can be a container name
   320  // or a partial/full ID.
   321  func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) error {
   322  	if options == nil {
   323  		options = new(UnpauseOptions)
   324  	}
   325  	_ = options
   326  	conn, err := bindings.GetClient(ctx)
   327  	if err != nil {
   328  		return err
   329  	}
   330  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID)
   331  	if err != nil {
   332  		return err
   333  	}
   334  	defer response.Body.Close()
   335  
   336  	return response.Process(nil)
   337  }
   338  
   339  // Wait blocks until the given container reaches a condition. If not provided, the condition will
   340  // default to stopped.  If the condition is stopped, an exit code for the container will be provided. The
   341  // nameOrID can be a container name or a partial/full ID.
   342  func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, error) { // nolint
   343  	if options == nil {
   344  		options = new(WaitOptions)
   345  	}
   346  	var exitCode int32
   347  	conn, err := bindings.GetClient(ctx)
   348  	if err != nil {
   349  		return exitCode, err
   350  	}
   351  	params, err := options.ToParams()
   352  	if err != nil {
   353  		return exitCode, err
   354  	}
   355  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID)
   356  	if err != nil {
   357  		return exitCode, err
   358  	}
   359  	defer response.Body.Close()
   360  
   361  	return exitCode, response.Process(&exitCode)
   362  }
   363  
   364  // Exists is a quick, light-weight way to determine if a given container
   365  // exists in local storage.  The nameOrID can be a container name
   366  // or a partial/full ID.
   367  func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, error) {
   368  	conn, err := bindings.GetClient(ctx)
   369  	if err != nil {
   370  		return false, err
   371  	}
   372  	params, err := options.ToParams()
   373  	if err != nil {
   374  		return false, err
   375  	}
   376  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/exists", params, nil, nameOrID)
   377  	if err != nil {
   378  		return false, err
   379  	}
   380  	defer response.Body.Close()
   381  
   382  	return response.IsSuccess(), nil
   383  }
   384  
   385  // Stop stops a running container.  The timeout is optional. The nameOrID can be a container name
   386  // or a partial/full ID
   387  func Stop(ctx context.Context, nameOrID string, options *StopOptions) error {
   388  	if options == nil {
   389  		options = new(StopOptions)
   390  	}
   391  	params, err := options.ToParams()
   392  	if err != nil {
   393  		return err
   394  	}
   395  	conn, err := bindings.GetClient(ctx)
   396  	if err != nil {
   397  		return err
   398  	}
   399  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID)
   400  	if err != nil {
   401  		return err
   402  	}
   403  	defer response.Body.Close()
   404  
   405  	return response.Process(nil)
   406  }
   407  
   408  // Export creates a tarball of the given name or ID of a container.  It
   409  // requires an io.Writer be provided to write the tarball.
   410  func Export(ctx context.Context, nameOrID string, w io.Writer, options *ExportOptions) error {
   411  	if options == nil {
   412  		options = new(ExportOptions)
   413  	}
   414  	_ = options
   415  	params := url.Values{}
   416  	conn, err := bindings.GetClient(ctx)
   417  	if err != nil {
   418  		return err
   419  	}
   420  	response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID)
   421  	if err != nil {
   422  		return err
   423  	}
   424  	defer response.Body.Close()
   425  
   426  	if response.StatusCode/100 == 2 {
   427  		_, err = io.Copy(w, response.Body)
   428  		return err
   429  	}
   430  	return response.Process(nil)
   431  }
   432  
   433  // ContainerInit takes a created container and executes all of the
   434  // preparations to run the container except it will not start
   435  // or attach to the container
   436  func ContainerInit(ctx context.Context, nameOrID string, options *InitOptions) error {
   437  	if options == nil {
   438  		options = new(InitOptions)
   439  	}
   440  	_ = options
   441  	conn, err := bindings.GetClient(ctx)
   442  	if err != nil {
   443  		return err
   444  	}
   445  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID)
   446  	if err != nil {
   447  		return err
   448  	}
   449  	defer response.Body.Close()
   450  
   451  	if response.StatusCode == http.StatusNotModified {
   452  		return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has already been created in runtime", nameOrID)
   453  	}
   454  	return response.Process(nil)
   455  }
   456  
   457  func ShouldRestart(ctx context.Context, nameOrID string, options *ShouldRestartOptions) (bool, error) {
   458  	if options == nil {
   459  		options = new(ShouldRestartOptions)
   460  	}
   461  	_ = options
   462  	conn, err := bindings.GetClient(ctx)
   463  	if err != nil {
   464  		return false, err
   465  	}
   466  	response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID)
   467  	if err != nil {
   468  		return false, err
   469  	}
   470  	defer response.Body.Close()
   471  
   472  	return response.IsSuccess(), nil
   473  }