github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/domain/infra/tunnel/containers.go (about) 1 package tunnel 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/containers/common/pkg/config" 14 "github.com/containers/image/v5/docker/reference" 15 "github.com/hanks177/podman/v4/libpod/define" 16 "github.com/hanks177/podman/v4/libpod/events" 17 "github.com/hanks177/podman/v4/pkg/api/handlers" 18 "github.com/hanks177/podman/v4/pkg/bindings/containers" 19 "github.com/hanks177/podman/v4/pkg/bindings/images" 20 "github.com/hanks177/podman/v4/pkg/domain/entities" 21 "github.com/hanks177/podman/v4/pkg/domain/entities/reports" 22 "github.com/hanks177/podman/v4/pkg/errorhandling" 23 "github.com/hanks177/podman/v4/pkg/specgen" 24 "github.com/hanks177/podman/v4/pkg/util" 25 "github.com/containers/storage/types" 26 "github.com/pkg/errors" 27 "github.com/sirupsen/logrus" 28 ) 29 30 func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, image string, args []string, options entities.ContainerRunlabelOptions) error { 31 return errors.New("not implemented") 32 } 33 34 func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string, options entities.ContainerExistsOptions) (*entities.BoolReport, error) { 35 exists, err := containers.Exists(ic.ClientCtx, nameOrID, new(containers.ExistsOptions).WithExternal(options.External)) 36 return &entities.BoolReport{Value: exists}, err 37 } 38 39 func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, opts entities.WaitOptions) ([]entities.WaitReport, error) { 40 cons, err := getContainersByContext(ic.ClientCtx, false, false, namesOrIds) 41 if err != nil { 42 return nil, err 43 } 44 responses := make([]entities.WaitReport, 0, len(cons)) 45 options := new(containers.WaitOptions).WithCondition(opts.Condition).WithInterval(opts.Interval.String()) 46 for _, c := range cons { 47 response := entities.WaitReport{Id: c.ID} 48 exitCode, err := containers.Wait(ic.ClientCtx, c.ID, options) 49 if err != nil { 50 response.Error = err 51 } else { 52 response.ExitCode = exitCode 53 } 54 responses = append(responses, response) 55 } 56 return responses, nil 57 } 58 59 func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { 60 ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) 61 if err != nil { 62 return nil, err 63 } 64 reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) 65 for _, c := range ctrs { 66 err := containers.Pause(ic.ClientCtx, c.ID, nil) 67 if err != nil && options.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { 68 logrus.Debugf("Container %s is not running", c.ID) 69 continue 70 } 71 reports = append(reports, &entities.PauseUnpauseReport{Id: c.ID, Err: err}) 72 } 73 return reports, nil 74 } 75 76 func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { 77 reports := []*entities.PauseUnpauseReport{} 78 ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) 79 if err != nil { 80 return nil, err 81 } 82 for _, c := range ctrs { 83 err := containers.Unpause(ic.ClientCtx, c.ID, nil) 84 if err != nil && options.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { 85 logrus.Debugf("Container %s is not paused", c.ID) 86 continue 87 } 88 reports = append(reports, &entities.PauseUnpauseReport{Id: c.ID, Err: err}) 89 } 90 return reports, nil 91 } 92 93 func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, opts entities.StopOptions) ([]*entities.StopReport, error) { 94 reports := []*entities.StopReport{} 95 ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, namesOrIds) 96 if err != nil { 97 return nil, err 98 } 99 ctrMap := map[string]string{} 100 for i := range ctrs { 101 ctrMap[ctrs[i].ID] = rawInputs[i] 102 } 103 options := new(containers.StopOptions).WithIgnore(opts.Ignore) 104 if to := opts.Timeout; to != nil { 105 options.WithTimeout(*to) 106 } 107 for _, c := range ctrs { 108 report := entities.StopReport{ 109 Id: c.ID, 110 RawInput: ctrMap[c.ID], 111 } 112 if err = containers.Stop(ic.ClientCtx, c.ID, options); err != nil { 113 // These first two are considered non-fatal under the right conditions 114 if errors.Cause(err).Error() == define.ErrCtrStopped.Error() { 115 logrus.Debugf("Container %s is already stopped", c.ID) 116 reports = append(reports, &report) 117 continue 118 } else if opts.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { 119 logrus.Debugf("Container %s is not running, could not stop", c.ID) 120 reports = append(reports, &report) 121 continue 122 } 123 124 // TODO we need to associate errors returned by http with common 125 // define.errors so that we can equity tests. this will allow output 126 // to be the same as the native client 127 report.Err = err 128 reports = append(reports, &report) 129 continue 130 } 131 reports = append(reports, &report) 132 } 133 return reports, nil 134 } 135 136 func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, opts entities.KillOptions) ([]*entities.KillReport, error) { 137 ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds) 138 if err != nil { 139 return nil, err 140 } 141 ctrMap := map[string]string{} 142 for i := range ctrs { 143 ctrMap[ctrs[i].ID] = rawInputs[i] 144 } 145 options := new(containers.KillOptions).WithSignal(opts.Signal) 146 reports := make([]*entities.KillReport, 0, len(ctrs)) 147 for _, c := range ctrs { 148 err := containers.Kill(ic.ClientCtx, c.ID, options) 149 if err != nil && opts.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { 150 logrus.Debugf("Container %s is not running", c.ID) 151 continue 152 } 153 reports = append(reports, &entities.KillReport{ 154 Id: c.ID, 155 Err: err, 156 RawInput: ctrMap[c.ID], 157 }) 158 } 159 return reports, nil 160 } 161 162 func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []string, opts entities.RestartOptions) ([]*entities.RestartReport, error) { 163 var ( 164 reports = []*entities.RestartReport{} 165 ) 166 options := new(containers.RestartOptions) 167 if to := opts.Timeout; to != nil { 168 options.WithTimeout(int(*to)) 169 } 170 ctrs, err := getContainersByContext(ic.ClientCtx, opts.All, false, namesOrIds) 171 if err != nil { 172 return nil, err 173 } 174 for _, c := range ctrs { 175 if opts.Running && c.State != define.ContainerStateRunning.String() { 176 continue 177 } 178 reports = append(reports, &entities.RestartReport{ 179 Id: c.ID, 180 Err: containers.Restart(ic.ClientCtx, c.ID, options), 181 }) 182 } 183 return reports, nil 184 } 185 186 func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, opts entities.RmOptions) ([]*reports.RmReport, error) { 187 // TODO there is no endpoint for container eviction. Need to discuss 188 options := new(containers.RemoveOptions).WithForce(opts.Force).WithVolumes(opts.Volumes).WithIgnore(opts.Ignore).WithDepend(opts.Depend) 189 if opts.Timeout != nil { 190 options = options.WithTimeout(*opts.Timeout) 191 } 192 193 toRemove := []string{} 194 alreadyRemoved := make(map[string]bool) // Avoids trying to remove already removed containers 195 if opts.All { 196 ctrs, err := getContainersByContext(ic.ClientCtx, opts.All, opts.Ignore, nil) 197 if err != nil { 198 return nil, err 199 } 200 for _, c := range ctrs { 201 toRemove = append(toRemove, c.ID) 202 } 203 } else { 204 for _, ctr := range namesOrIds { 205 // NOTE that we set ignore=true here to support 206 // removing external containers (e.g., Buildah 207 // containers). If we don't find the container, 208 // we'll use the raw input provided by the user 209 // instead of the ID. Since this can only happen 210 // with external containers, it poses no threat 211 // to the `alreadyRemoved` checks below. 212 ctrs, err := getContainersByContext(ic.ClientCtx, false, true, []string{ctr}) 213 if err != nil { 214 return nil, err 215 } 216 id := ctr 217 if len(ctrs) == 1 { 218 id = ctrs[0].ID 219 } 220 toRemove = append(toRemove, id) 221 } 222 } 223 224 rmReports := make([]*reports.RmReport, 0, len(toRemove)) 225 for _, nameOrID := range toRemove { 226 if alreadyRemoved[nameOrID] { 227 continue 228 } 229 newReports, err := containers.Remove(ic.ClientCtx, nameOrID, options) 230 if err != nil { 231 rmReports = append(rmReports, &reports.RmReport{Id: nameOrID, Err: err}) 232 continue 233 } 234 for i := range newReports { 235 alreadyRemoved[newReports[i].Id] = true 236 rmReports = append(rmReports, newReports[i]) 237 } 238 } 239 return rmReports, nil 240 } 241 242 func (ic *ContainerEngine) ContainerPrune(ctx context.Context, opts entities.ContainerPruneOptions) ([]*reports.PruneReport, error) { 243 options := new(containers.PruneOptions).WithFilters(opts.Filters) 244 return containers.Prune(ic.ClientCtx, options) 245 } 246 247 func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, opts entities.InspectOptions) ([]*entities.ContainerInspectReport, []error, error) { 248 var ( 249 reports = make([]*entities.ContainerInspectReport, 0, len(namesOrIds)) 250 errs = []error{} 251 ) 252 options := new(containers.InspectOptions).WithSize(opts.Size) 253 for _, name := range namesOrIds { 254 inspect, err := containers.Inspect(ic.ClientCtx, name, options) 255 if err != nil { 256 errModel, ok := err.(*errorhandling.ErrorModel) 257 if !ok { 258 return nil, nil, err 259 } 260 if errModel.ResponseCode == 404 { 261 errs = append(errs, errors.Errorf("no such container %q", name)) 262 continue 263 } 264 return nil, nil, err 265 } 266 reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: inspect}) 267 } 268 return reports, errs, nil 269 } 270 271 func (ic *ContainerEngine) ContainerTop(ctx context.Context, opts entities.TopOptions) (*entities.StringSliceReport, error) { 272 switch { 273 case opts.Latest: 274 return nil, errors.New("latest is not supported") 275 case opts.NameOrID == "": 276 return nil, errors.New("NameOrID must be specified") 277 } 278 options := new(containers.TopOptions).WithDescriptors(opts.Descriptors) 279 topOutput, err := containers.Top(ic.ClientCtx, opts.NameOrID, options) 280 if err != nil { 281 return nil, err 282 } 283 return &entities.StringSliceReport{Value: topOutput}, nil 284 } 285 286 func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, opts entities.CommitOptions) (*entities.CommitReport, error) { 287 var ( 288 repo string 289 tag = "latest" 290 ) 291 if len(opts.ImageName) > 0 { 292 ref, err := reference.Parse(opts.ImageName) 293 if err != nil { 294 return nil, errors.Wrapf(err, "error parsing reference %q", opts.ImageName) 295 } 296 if t, ok := ref.(reference.Tagged); ok { 297 tag = t.Tag() 298 } 299 if r, ok := ref.(reference.Named); ok { 300 repo = r.Name() 301 } 302 if len(repo) < 1 { 303 return nil, errors.Errorf("invalid image name %q", opts.ImageName) 304 } 305 } 306 options := new(containers.CommitOptions).WithAuthor(opts.Author).WithChanges(opts.Changes).WithComment(opts.Message).WithSquash(opts.Squash) 307 options.WithFormat(opts.Format).WithPause(opts.Pause).WithRepo(repo).WithTag(tag) 308 response, err := containers.Commit(ic.ClientCtx, nameOrID, options) 309 if err != nil { 310 return nil, err 311 } 312 return &entities.CommitReport{Id: response.ID}, nil 313 } 314 315 func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrID string, options entities.ContainerExportOptions) error { 316 var ( 317 err error 318 w io.Writer 319 ) 320 if len(options.Output) > 0 { 321 w, err = os.Create(options.Output) 322 if err != nil { 323 return err 324 } 325 } 326 return containers.Export(ic.ClientCtx, nameOrID, w, nil) 327 } 328 329 func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, opts entities.CheckpointOptions) ([]*entities.CheckpointReport, error) { 330 options := new(containers.CheckpointOptions) 331 options.WithFileLocks(opts.FileLocks) 332 options.WithIgnoreRootfs(opts.IgnoreRootFS) 333 options.WithKeep(opts.Keep) 334 options.WithExport(opts.Export) 335 options.WithCreateImage(opts.CreateImage) 336 options.WithTCPEstablished(opts.TCPEstablished) 337 options.WithPrintStats(opts.PrintStats) 338 options.WithPreCheckpoint(opts.PreCheckPoint) 339 options.WithLeaveRunning(opts.LeaveRunning) 340 options.WithWithPrevious(opts.WithPrevious) 341 342 var ( 343 err error 344 ctrs = []entities.ListContainer{} 345 ) 346 347 if opts.All { 348 allCtrs, err := getContainersByContext(ic.ClientCtx, true, false, []string{}) 349 if err != nil { 350 return nil, err 351 } 352 // narrow the list to running only 353 for _, c := range allCtrs { 354 if c.State == define.ContainerStateRunning.String() { 355 ctrs = append(ctrs, c) 356 } 357 } 358 } else { 359 ctrs, err = getContainersByContext(ic.ClientCtx, false, false, namesOrIds) 360 if err != nil { 361 return nil, err 362 } 363 } 364 reports := make([]*entities.CheckpointReport, 0, len(ctrs)) 365 for _, c := range ctrs { 366 report, err := containers.Checkpoint(ic.ClientCtx, c.ID, options) 367 if err != nil { 368 reports = append(reports, &entities.CheckpointReport{Id: c.ID, Err: err}) 369 } else { 370 reports = append(reports, report) 371 } 372 } 373 return reports, nil 374 } 375 376 func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, opts entities.RestoreOptions) ([]*entities.RestoreReport, error) { 377 if opts.ImportPrevious != "" { 378 return nil, fmt.Errorf("--import-previous is not supported on the remote client") 379 } 380 381 options := new(containers.RestoreOptions) 382 options.WithFileLocks(opts.FileLocks) 383 options.WithIgnoreRootfs(opts.IgnoreRootFS) 384 options.WithIgnoreVolumes(opts.IgnoreVolumes) 385 options.WithIgnoreStaticIP(opts.IgnoreStaticIP) 386 options.WithIgnoreStaticMAC(opts.IgnoreStaticMAC) 387 options.WithKeep(opts.Keep) 388 options.WithName(opts.Name) 389 options.WithTCPEstablished(opts.TCPEstablished) 390 options.WithPod(opts.Pod) 391 options.WithPrintStats(opts.PrintStats) 392 options.WithPublishPorts(opts.PublishPorts) 393 394 if opts.Import != "" { 395 options.WithImportArchive(opts.Import) 396 report, err := containers.Restore(ic.ClientCtx, "", options) 397 return []*entities.RestoreReport{report}, err 398 } 399 400 var ( 401 ids = []string{} 402 ) 403 if opts.All { 404 allCtrs, err := getContainersByContext(ic.ClientCtx, true, false, []string{}) 405 if err != nil { 406 return nil, err 407 } 408 // narrow the list to exited only 409 for _, c := range allCtrs { 410 if c.State == define.ContainerStateExited.String() { 411 ids = append(ids, c.ID) 412 } 413 } 414 } else { 415 getImageOptions := new(images.GetOptions).WithSize(false) 416 hostInfo, err := ic.Info(context.Background()) 417 if err != nil { 418 return nil, err 419 } 420 421 for _, nameOrID := range namesOrIds { 422 ctrData, _, err := ic.ContainerInspect(ic.ClientCtx, []string{nameOrID}, entities.InspectOptions{}) 423 if err == nil && len(ctrData) > 0 { 424 ids = append(ids, ctrData[0].ID) 425 } else { 426 // If container was not found, check if this is a checkpoint image 427 inspectReport, err := images.GetImage(ic.ClientCtx, nameOrID, getImageOptions) 428 if err != nil { 429 return nil, fmt.Errorf("no such container or image: %s", nameOrID) 430 } 431 checkpointRuntimeName, found := inspectReport.Annotations[define.CheckpointAnnotationRuntimeName] 432 if !found { 433 return nil, fmt.Errorf("image is not a checkpoint: %s", nameOrID) 434 } 435 if hostInfo.Host.OCIRuntime.Name != checkpointRuntimeName { 436 return nil, fmt.Errorf("container image \"%s\" requires runtime: \"%s\"", nameOrID, checkpointRuntimeName) 437 } 438 ids = append(ids, inspectReport.ID) 439 } 440 } 441 } 442 reports := make([]*entities.RestoreReport, 0, len(ids)) 443 for _, id := range ids { 444 report, err := containers.Restore(ic.ClientCtx, id, options) 445 if err != nil { 446 reports = append(reports, &entities.RestoreReport{Id: id, Err: err}) 447 } 448 reports = append(reports, report) 449 } 450 return reports, nil 451 } 452 453 func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) { 454 response, err := containers.CreateWithSpec(ic.ClientCtx, s, nil) 455 if err != nil { 456 return nil, err 457 } 458 for _, w := range response.Warnings { 459 fmt.Fprintf(os.Stderr, "%s\n", w) 460 } 461 return &entities.ContainerCreateReport{Id: response.ID}, nil 462 } 463 464 func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, opts entities.ContainerLogsOptions) error { 465 since := opts.Since.Format(time.RFC3339) 466 until := opts.Until.Format(time.RFC3339) 467 tail := strconv.FormatInt(opts.Tail, 10) 468 stdout := opts.StdoutWriter != nil 469 stderr := opts.StderrWriter != nil 470 options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithUntil(until).WithStderr(stderr) 471 options.WithStdout(stdout).WithTail(tail) 472 473 var err error 474 stdoutCh := make(chan string) 475 stderrCh := make(chan string) 476 ctx, cancel := context.WithCancel(context.Background()) 477 go func() { 478 err = containers.Logs(ic.ClientCtx, nameOrIDs[0], options, stdoutCh, stderrCh) 479 cancel() 480 }() 481 482 for { 483 select { 484 case <-ctx.Done(): 485 return err 486 case line := <-stdoutCh: 487 if opts.StdoutWriter != nil { 488 _, _ = io.WriteString(opts.StdoutWriter, line) 489 } 490 case line := <-stderrCh: 491 if opts.StderrWriter != nil { 492 _, _ = io.WriteString(opts.StderrWriter, line) 493 } 494 } 495 } 496 } 497 498 func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrID string, opts entities.AttachOptions) error { 499 ctrs, err := getContainersByContext(ic.ClientCtx, false, false, []string{nameOrID}) 500 if err != nil { 501 return err 502 } 503 ctr := ctrs[0] 504 if ctr.State != define.ContainerStateRunning.String() { 505 return errors.Errorf("you can only attach to running containers") 506 } 507 options := new(containers.AttachOptions).WithStream(true).WithDetachKeys(opts.DetachKeys) 508 return containers.Attach(ic.ClientCtx, nameOrID, opts.Stdin, opts.Stdout, opts.Stderr, nil, options) 509 } 510 511 func makeExecConfig(options entities.ExecOptions) *handlers.ExecCreateConfig { 512 env := []string{} 513 for k, v := range options.Envs { 514 env = append(env, fmt.Sprintf("%s=%s", k, v)) 515 } 516 517 createConfig := new(handlers.ExecCreateConfig) 518 createConfig.User = options.User 519 createConfig.Privileged = options.Privileged 520 createConfig.Tty = options.Tty 521 createConfig.AttachStdin = options.Interactive 522 createConfig.AttachStdout = true 523 createConfig.AttachStderr = true 524 createConfig.Detach = false 525 createConfig.DetachKeys = options.DetachKeys 526 createConfig.Env = env 527 createConfig.WorkingDir = options.WorkDir 528 createConfig.Cmd = options.Cmd 529 530 return createConfig 531 } 532 533 func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, options entities.ExecOptions, streams define.AttachStreams) (int, error) { 534 createConfig := makeExecConfig(options) 535 536 sessionID, err := containers.ExecCreate(ic.ClientCtx, nameOrID, createConfig) 537 if err != nil { 538 return 125, err 539 } 540 startAndAttachOptions := new(containers.ExecStartAndAttachOptions) 541 startAndAttachOptions.WithOutputStream(streams.OutputStream).WithErrorStream(streams.ErrorStream) 542 if streams.InputStream != nil { 543 startAndAttachOptions.WithInputStream(*streams.InputStream) 544 } 545 startAndAttachOptions.WithAttachError(streams.AttachError).WithAttachOutput(streams.AttachOutput).WithAttachInput(streams.AttachInput) 546 if err := containers.ExecStartAndAttach(ic.ClientCtx, sessionID, startAndAttachOptions); err != nil { 547 return 125, err 548 } 549 550 inspectOut, err := containers.ExecInspect(ic.ClientCtx, sessionID, nil) 551 if err != nil { 552 return 125, err 553 } 554 555 return inspectOut.ExitCode, nil 556 } 557 558 func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (string, error) { 559 createConfig := makeExecConfig(options) 560 561 sessionID, err := containers.ExecCreate(ic.ClientCtx, nameOrID, createConfig) 562 if err != nil { 563 return "", err 564 } 565 566 if err := containers.ExecStart(ic.ClientCtx, sessionID, nil); err != nil { 567 return "", err 568 } 569 570 return sessionID, nil 571 } 572 573 func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { //nolint 574 attachErr := make(chan error) 575 attachReady := make(chan bool) 576 options := new(containers.AttachOptions).WithStream(true) 577 if dk := detachKeys; dk != nil { 578 options.WithDetachKeys(*dk) 579 } 580 go func() { 581 err := containers.Attach(ic.ClientCtx, name, input, output, errput, attachReady, options) 582 attachErr <- err 583 }() 584 // Wait for the attach to actually happen before starting 585 // the container. 586 select { 587 case <-attachReady: 588 startOptions := new(containers.StartOptions) 589 if dk := detachKeys; dk != nil { 590 startOptions.WithDetachKeys(*dk) 591 } 592 if err := containers.Start(ic.ClientCtx, name, startOptions); err != nil { 593 return err 594 } 595 case err := <-attachErr: 596 return err 597 } 598 // If attachReady happens first, wait for containers.Attach to complete 599 return <-attachErr 600 } 601 602 func logIfRmError(id string, err error, reports []*reports.RmReport) { 603 logError := func(id string, err error) { 604 if errorhandling.Contains(err, define.ErrNoSuchCtr) || 605 errorhandling.Contains(err, define.ErrCtrRemoved) || 606 errorhandling.Contains(err, types.ErrLayerUnknown) { 607 logrus.Debugf("Container %s does not exist: %v", id, err) 608 } else { 609 logrus.Errorf("Removing container %s: %v", id, err) 610 } 611 } 612 if err != nil { 613 logError(id, err) 614 } else { 615 for _, report := range reports { 616 if report.Err != nil { 617 logError(report.Id, report.Err) 618 } 619 } 620 } 621 } 622 623 func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { 624 reports := []*entities.ContainerStartReport{} 625 var exitCode = define.ExecErrorCodeGeneric 626 containersNamesOrIds := namesOrIds 627 all := options.All 628 if len(options.Filters) > 0 { 629 all = false 630 containersNamesOrIds = []string{} 631 opts := new(containers.ListOptions).WithFilters(options.Filters).WithAll(true) 632 candidates, listErr := containers.List(ic.ClientCtx, opts) 633 if listErr != nil { 634 return nil, listErr 635 } 636 for _, candidate := range candidates { 637 if options.All { 638 containersNamesOrIds = append(containersNamesOrIds, candidate.ID) 639 continue 640 } 641 for _, nameOrID := range namesOrIds { 642 if nameOrID == candidate.ID { 643 containersNamesOrIds = append(containersNamesOrIds, nameOrID) 644 continue 645 } 646 for _, containerName := range candidate.Names { 647 if containerName == nameOrID { 648 containersNamesOrIds = append(containersNamesOrIds, nameOrID) 649 continue 650 } 651 } 652 } 653 } 654 } 655 ctrs, err := getContainersByContext(ic.ClientCtx, all, false, containersNamesOrIds) 656 if err != nil { 657 return nil, err 658 } 659 removeOptions := new(containers.RemoveOptions).WithVolumes(true).WithForce(false) 660 removeContainer := func(id string) { 661 reports, err := containers.Remove(ic.ClientCtx, id, removeOptions) 662 logIfRmError(id, err, reports) 663 } 664 665 // There can only be one container if attach was used 666 for i, ctr := range ctrs { 667 name := ctr.ID 668 rawInput := ctr.ID 669 if !options.All { 670 rawInput = namesOrIds[i] 671 } 672 report := entities.ContainerStartReport{ 673 Id: name, 674 RawInput: rawInput, 675 ExitCode: exitCode, 676 } 677 ctrRunning := ctr.State == define.ContainerStateRunning.String() 678 if options.Attach { 679 err = startAndAttach(ic, name, &options.DetachKeys, options.Stdin, options.Stdout, options.Stderr) 680 if err == define.ErrDetach { 681 // User manually detached 682 // Exit cleanly immediately 683 reports = append(reports, &report) 684 return reports, nil 685 } 686 if ctrRunning { 687 reports = append(reports, &report) 688 return reports, nil 689 } 690 691 if err != nil { 692 if ctr.AutoRemove { 693 removeContainer(ctr.ID) 694 } 695 report.ExitCode = define.ExitCode(report.Err) 696 report.Err = err 697 reports = append(reports, &report) 698 return reports, errors.Wrapf(report.Err, "unable to start container %s", name) 699 } 700 if ctr.AutoRemove { 701 // Defer the removal, so we can return early if needed and 702 // de-spaghetti the code. 703 defer func() { 704 shouldRestart, err := containers.ShouldRestart(ic.ClientCtx, ctr.ID, nil) 705 if err != nil { 706 logrus.Errorf("Failed to check if %s should restart: %v", ctr.ID, err) 707 return 708 } 709 logrus.Errorf("Should restart: %v", shouldRestart) 710 711 if !shouldRestart && ctr.AutoRemove { 712 removeContainer(ctr.ID) 713 } 714 }() 715 } 716 717 exitCode, err := containers.Wait(ic.ClientCtx, name, nil) 718 if err == define.ErrNoSuchCtr { 719 // Check events 720 event, err := ic.GetLastContainerEvent(ctx, name, events.Exited) 721 if err != nil { 722 logrus.Errorf("Cannot get exit code: %v", err) 723 report.ExitCode = define.ExecErrorCodeNotFound 724 } else { 725 report.ExitCode = event.ContainerExitCode 726 } 727 } else { 728 report.ExitCode = int(exitCode) 729 } 730 reports = append(reports, &report) 731 return reports, nil 732 } 733 // Start the container if it's not running already. 734 if !ctrRunning { 735 err = containers.Start(ic.ClientCtx, name, new(containers.StartOptions).WithDetachKeys(options.DetachKeys)) 736 if err != nil { 737 if ctr.AutoRemove { 738 rmOptions := new(containers.RemoveOptions).WithForce(false).WithVolumes(true) 739 reports, err := containers.Remove(ic.ClientCtx, ctr.ID, rmOptions) 740 logIfRmError(ctr.ID, err, reports) 741 } 742 report.Err = errors.Wrapf(err, "unable to start container %q", name) 743 report.ExitCode = define.ExitCode(err) 744 reports = append(reports, &report) 745 continue 746 } 747 report.ExitCode = 0 748 reports = append(reports, &report) 749 } 750 } 751 return reports, nil 752 } 753 754 func (ic *ContainerEngine) ContainerList(ctx context.Context, opts entities.ContainerListOptions) ([]entities.ListContainer, error) { 755 options := new(containers.ListOptions).WithFilters(opts.Filters).WithAll(opts.All).WithLast(opts.Last) 756 options.WithNamespace(opts.Namespace).WithSize(opts.Size).WithSync(opts.Sync).WithExternal(opts.External) 757 return containers.List(ic.ClientCtx, options) 758 } 759 760 func (ic *ContainerEngine) ContainerListExternal(ctx context.Context) ([]entities.ListContainer, error) { 761 options := new(containers.ListOptions).WithAll(true) 762 options.WithNamespace(true).WithSize(true).WithSync(true).WithExternal(true) 763 return containers.List(ic.ClientCtx, options) 764 } 765 766 func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { 767 con, err := containers.CreateWithSpec(ic.ClientCtx, opts.Spec, nil) 768 if err != nil { 769 return nil, err 770 } 771 for _, w := range con.Warnings { 772 fmt.Fprintf(os.Stderr, "%s\n", w) 773 } 774 if opts.CIDFile != "" { 775 if err := util.CreateCidFile(opts.CIDFile, con.ID); err != nil { 776 return nil, err 777 } 778 } 779 780 report := entities.ContainerRunReport{Id: con.ID} 781 782 if opts.Detach { 783 // Detach and return early 784 err := containers.Start(ic.ClientCtx, con.ID, new(containers.StartOptions).WithRecursive(true)) 785 if err != nil { 786 report.ExitCode = define.ExitCode(err) 787 } 788 return &report, err 789 } 790 791 // Attach 792 if err := startAndAttach(ic, con.ID, &opts.DetachKeys, opts.InputStream, opts.OutputStream, opts.ErrorStream); err != nil { 793 if err == define.ErrDetach { 794 return &report, nil 795 } 796 797 report.ExitCode = define.ExitCode(err) 798 if opts.Rm { 799 reports, rmErr := containers.Remove(ic.ClientCtx, con.ID, new(containers.RemoveOptions).WithForce(false).WithVolumes(true)) 800 if rmErr != nil || reports[0].Err != nil { 801 logrus.Debugf("unable to remove container %s after failing to start and attach to it", con.ID) 802 } 803 } 804 return &report, err 805 } 806 807 if opts.Rm { 808 // Defer the removal, so we can return early if needed and 809 // de-spaghetti the code. 810 defer func() { 811 shouldRestart, err := containers.ShouldRestart(ic.ClientCtx, con.ID, nil) 812 if err != nil { 813 logrus.Errorf("Failed to check if %s should restart: %v", con.ID, err) 814 return 815 } 816 817 if !shouldRestart { 818 reports, err := containers.Remove(ic.ClientCtx, con.ID, new(containers.RemoveOptions).WithForce(false).WithVolumes(true)) 819 logIfRmError(con.ID, err, reports) 820 } 821 }() 822 } 823 824 // Wait 825 exitCode, waitErr := containers.Wait(ic.ClientCtx, con.ID, nil) 826 if waitErr == nil { 827 report.ExitCode = int(exitCode) 828 return &report, nil 829 } 830 831 // Determine why the wait failed. If the container doesn't exist, 832 // consult the events. 833 if !errorhandling.Contains(waitErr, define.ErrNoSuchCtr) { 834 return &report, waitErr 835 } 836 837 // Events 838 eventsChannel := make(chan *events.Event) 839 eventOptions := entities.EventsOptions{ 840 EventChan: eventsChannel, 841 Filter: []string{ 842 "type=container", 843 fmt.Sprintf("container=%s", con.ID), 844 fmt.Sprintf("event=%s", events.Exited), 845 }, 846 } 847 848 var lastEvent *events.Event 849 var mutex sync.Mutex 850 mutex.Lock() 851 // Read the events. 852 go func() { 853 for e := range eventsChannel { 854 lastEvent = e 855 } 856 mutex.Unlock() 857 }() 858 859 eventsErr := ic.Events(ctx, eventOptions) 860 861 // Wait for all events to be read 862 mutex.Lock() 863 if eventsErr != nil || lastEvent == nil { 864 logrus.Errorf("Cannot get exit code: %v", err) 865 report.ExitCode = define.ExecErrorCodeNotFound 866 return &report, nil // nolint: nilerr 867 } 868 869 report.ExitCode = lastEvent.ContainerExitCode 870 return &report, err 871 } 872 873 func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts entities.DiffOptions) (*entities.DiffReport, error) { 874 var base string 875 options := new(containers.DiffOptions).WithDiffType(opts.Type.String()) 876 if len(namesOrIDs) > 0 { 877 base = namesOrIDs[0] 878 if len(namesOrIDs) > 1 { 879 options.WithParent(namesOrIDs[1]) 880 } 881 } else { 882 return nil, errors.New("no arguments for diff") 883 } 884 changes, err := containers.Diff(ic.ClientCtx, base, options) 885 return &entities.DiffReport{Changes: changes}, err 886 } 887 888 func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []string, options entities.ContainerCleanupOptions) ([]*entities.ContainerCleanupReport, error) { 889 return nil, errors.New("not implemented") 890 } 891 892 func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []string, options entities.ContainerInitOptions) ([]*entities.ContainerInitReport, error) { 893 ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) 894 if err != nil { 895 return nil, err 896 } 897 reports := make([]*entities.ContainerInitReport, 0, len(ctrs)) 898 for _, ctr := range ctrs { 899 err := containers.ContainerInit(ic.ClientCtx, ctr.ID, nil) 900 // When using all, it is NOT considered an error if a container 901 // has already been init'd. 902 if err != nil && options.All && strings.Contains(errors.Cause(err).Error(), define.ErrCtrStateInvalid.Error()) { 903 err = nil 904 } 905 reports = append(reports, &entities.ContainerInitReport{ 906 Err: err, 907 Id: ctr.ID, 908 }) 909 } 910 return reports, nil 911 } 912 913 func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIDs []string, options entities.ContainerMountOptions) ([]*entities.ContainerMountReport, error) { 914 return nil, errors.New("mounting containers is not supported for remote clients") 915 } 916 917 func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIDs []string, options entities.ContainerUnmountOptions) ([]*entities.ContainerUnmountReport, error) { 918 return nil, errors.New("unmounting containers is not supported for remote clients") 919 } 920 921 func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { 922 return config.Default() 923 } 924 925 func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) { 926 var ( 927 reports = []*entities.ContainerPortReport{} 928 namesOrIds = []string{} 929 ) 930 if len(nameOrID) > 0 { 931 namesOrIds = append(namesOrIds, nameOrID) 932 } 933 ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) 934 if err != nil { 935 return nil, err 936 } 937 for _, con := range ctrs { 938 if con.State != define.ContainerStateRunning.String() { 939 continue 940 } 941 if len(con.Ports) > 0 { 942 reports = append(reports, &entities.ContainerPortReport{ 943 Id: con.ID, 944 Ports: con.Ports, 945 }) 946 } 947 } 948 return reports, nil 949 } 950 951 func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) { 952 copyOptions := new(containers.CopyOptions).WithChown(options.Chown).WithRename(options.Rename) 953 return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, copyOptions) 954 } 955 956 func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) { 957 return containers.CopyToArchive(ic.ClientCtx, nameOrID, path, writer) 958 } 959 960 func (ic *ContainerEngine) ContainerStat(ctx context.Context, nameOrID string, path string) (*entities.ContainerStatReport, error) { 961 return containers.Stat(ic.ClientCtx, nameOrID, path) 962 } 963 964 // Shutdown Libpod engine. 965 func (ic *ContainerEngine) Shutdown(_ context.Context) { 966 } 967 968 func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { 969 if options.Latest { 970 return nil, errors.New("latest is not supported for the remote client") 971 } 972 return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream).WithInterval(options.Interval)) 973 } 974 975 // ShouldRestart reports back whether the container will restart. 976 func (ic *ContainerEngine) ShouldRestart(_ context.Context, id string) (bool, error) { 977 return containers.ShouldRestart(ic.ClientCtx, id, nil) 978 } 979 980 // ContainerRename renames the given container. 981 func (ic *ContainerEngine) ContainerRename(ctx context.Context, nameOrID string, opts entities.ContainerRenameOptions) error { 982 return containers.Rename(ic.ClientCtx, nameOrID, new(containers.RenameOptions).WithName(opts.NewName)) 983 } 984 985 func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts entities.ContainerCloneOptions) (*entities.ContainerCreateReport, error) { 986 return nil, errors.New("cloning a container is not supported on the remote client") 987 }