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 }