package tunnel import ( "context" "errors" "fmt" "io" "os" "strconv" "strings" "sync" "time" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/api/handlers" "github.com/containers/podman/v4/pkg/bindings/containers" "github.com/containers/podman/v4/pkg/bindings/images" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/entities/reports" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage/types" "github.com/sirupsen/logrus" ) func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, image string, args []string, options entities.ContainerRunlabelOptions) error { return errors.New("not implemented") } func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string, options entities.ContainerExistsOptions) (*entities.BoolReport, error) { exists, err := containers.Exists(ic.ClientCtx, nameOrID, new(containers.ExistsOptions).WithExternal(options.External)) return &entities.BoolReport{Value: exists}, err } func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, opts entities.WaitOptions) ([]entities.WaitReport, error) { cons, err := getContainersByContext(ic.ClientCtx, false, false, namesOrIds) if err != nil { return nil, err } responses := make([]entities.WaitReport, 0, len(cons)) options := new(containers.WaitOptions).WithCondition(opts.Condition).WithInterval(opts.Interval.String()) for _, c := range cons { response := entities.WaitReport{Id: c.ID} exitCode, err := containers.Wait(ic.ClientCtx, c.ID, options) if err != nil { response.Error = err } else { response.ExitCode = exitCode } responses = append(responses, response) } return responses, nil } func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, options.All, false, namesOrIds, options.Filters) if err != nil { return nil, err } idToRawInput := map[string]string{} for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.Pause(ic.ClientCtx, c.ID, nil) if err != nil && options.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not running", c.ID) continue } reports = append(reports, &entities.PauseUnpauseReport{ Id: c.ID, Err: err, RawInput: idToRawInput[c.ID], }) } return reports, nil } func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, options.All, false, namesOrIds, options.Filters) if err != nil { return nil, err } idToRawInput := map[string]string{} for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.Unpause(ic.ClientCtx, c.ID, nil) if err != nil && options.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not paused", c.ID) continue } reports = append(reports, &entities.PauseUnpauseReport{ Id: c.ID, Err: err, RawInput: idToRawInput[c.ID], }) } return reports, nil } func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, opts entities.StopOptions) ([]*entities.StopReport, error) { ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, namesOrIds, opts.Filters) if err != nil { return nil, err } idToRawInput := map[string]string{} for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } options := new(containers.StopOptions).WithIgnore(opts.Ignore) if to := opts.Timeout; to != nil { options.WithTimeout(*to) } reports := []*entities.StopReport{} for _, c := range ctrs { report := entities.StopReport{ Id: c.ID, RawInput: idToRawInput[c.ID], } if err = containers.Stop(ic.ClientCtx, c.ID, options); err != nil { // These first two are considered non-fatal under the right conditions if strings.Contains(err.Error(), define.ErrCtrStopped.Error()) { logrus.Debugf("Container %s is already stopped", c.ID) reports = append(reports, &report) continue } else if opts.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not running, could not stop", c.ID) reports = append(reports, &report) continue } // TODO we need to associate errors returned by http with common // define.errors so that we can equity tests. this will allow output // to be the same as the native client report.Err = err reports = append(reports, &report) continue } reports = append(reports, &report) } return reports, nil } func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, opts entities.KillOptions) ([]*entities.KillReport, error) { ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds, nil) if err != nil { return nil, err } idToRawInput := map[string]string{} for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } options := new(containers.KillOptions).WithSignal(opts.Signal) reports := make([]*entities.KillReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.Kill(ic.ClientCtx, c.ID, options) if err != nil && opts.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not running", c.ID) continue } reports = append(reports, &entities.KillReport{ Id: c.ID, Err: err, RawInput: idToRawInput[c.ID], }) } return reports, nil } func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []string, opts entities.RestartOptions) ([]*entities.RestartReport, error) { var ( reports = []*entities.RestartReport{} ) options := new(containers.RestartOptions) if to := opts.Timeout; to != nil { options.WithTimeout(int(*to)) } ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds, opts.Filters) if err != nil { return nil, err } idToRawInput := map[string]string{} for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } for _, c := range ctrs { if opts.Running && c.State != define.ContainerStateRunning.String() { continue } reports = append(reports, &entities.RestartReport{ Id: c.ID, Err: containers.Restart(ic.ClientCtx, c.ID, options), RawInput: idToRawInput[c.ID], }) } return reports, nil } func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, opts entities.RmOptions) ([]*reports.RmReport, error) { // TODO there is no endpoint for container eviction. Need to discuss options := new(containers.RemoveOptions).WithForce(opts.Force).WithVolumes(opts.Volumes).WithIgnore(opts.Ignore).WithDepend(opts.Depend) if opts.Timeout != nil { options = options.WithTimeout(*opts.Timeout) } toRemove := []string{} alreadyRemoved := make(map[string]bool) // Avoids trying to remove already removed containers idToRawInput := map[string]string{} if opts.All || len(opts.Filters) > 0 { ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, nil, opts.Filters) if err != nil { return nil, err } if len(rawInputs) == len(ctrs) { for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } } for _, c := range ctrs { toRemove = append(toRemove, c.ID) } } else { for _, ctr := range namesOrIds { // NOTE that we set ignore=true here to support // removing external containers (e.g., Buildah // containers). If we don't find the container, // we'll use the raw input provided by the user // instead of the ID. Since this can only happen // with external containers, it poses no threat // to the `alreadyRemoved` checks below. ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, false, true, []string{ctr}, opts.Filters) if err != nil { return nil, err } if len(rawInputs) == len(ctrs) { for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } } id := ctr if len(ctrs) == 1 { id = ctrs[0].ID } toRemove = append(toRemove, id) } } rmReports := make([]*reports.RmReport, 0, len(toRemove)) for _, rmCtr := range toRemove { if alreadyRemoved[rmCtr] { continue } if ctr, exist := idToRawInput[rmCtr]; exist { rmCtr = ctr } newReports, err := containers.Remove(ic.ClientCtx, rmCtr, options) if err != nil { rmReports = append(rmReports, &reports.RmReport{ Id: rmCtr, Err: err, RawInput: idToRawInput[rmCtr], }) continue } for i := range newReports { alreadyRemoved[newReports[i].Id] = true rmReports = append(rmReports, newReports[i]) } } return rmReports, nil } func (ic *ContainerEngine) ContainerPrune(ctx context.Context, opts entities.ContainerPruneOptions) ([]*reports.PruneReport, error) { options := new(containers.PruneOptions).WithFilters(opts.Filters) return containers.Prune(ic.ClientCtx, options) } func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, opts entities.InspectOptions) ([]*entities.ContainerInspectReport, []error, error) { var ( reports = make([]*entities.ContainerInspectReport, 0, len(namesOrIds)) errs = []error{} ) options := new(containers.InspectOptions).WithSize(opts.Size) for _, name := range namesOrIds { inspect, err := containers.Inspect(ic.ClientCtx, name, options) if err != nil { errModel, ok := err.(*errorhandling.ErrorModel) if !ok { return nil, nil, err } if errModel.ResponseCode == 404 { errs = append(errs, fmt.Errorf("no such container %q", name)) continue } return nil, nil, err } reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: inspect}) } return reports, errs, nil } func (ic *ContainerEngine) ContainerTop(ctx context.Context, opts entities.TopOptions) (*entities.StringSliceReport, error) { switch { case opts.Latest: return nil, errors.New("latest is not supported") case opts.NameOrID == "": return nil, errors.New("NameOrID must be specified") } options := new(containers.TopOptions).WithDescriptors(opts.Descriptors) topOutput, err := containers.Top(ic.ClientCtx, opts.NameOrID, options) if err != nil { return nil, err } return &entities.StringSliceReport{Value: topOutput}, nil } func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, opts entities.CommitOptions) (*entities.CommitReport, error) { var ( repo string tag = "latest" ) if len(opts.ImageName) > 0 { ref, err := reference.Parse(opts.ImageName) if err != nil { return nil, fmt.Errorf("error parsing reference %q: %w", opts.ImageName, err) } if t, ok := ref.(reference.Tagged); ok { tag = t.Tag() } if r, ok := ref.(reference.Named); ok { repo = r.Name() } if len(repo) < 1 { return nil, fmt.Errorf("invalid image name %q", opts.ImageName) } } options := new(containers.CommitOptions).WithAuthor(opts.Author).WithChanges(opts.Changes).WithComment(opts.Message).WithSquash(opts.Squash) options.WithFormat(opts.Format).WithPause(opts.Pause).WithRepo(repo).WithTag(tag) response, err := containers.Commit(ic.ClientCtx, nameOrID, options) if err != nil { return nil, err } return &entities.CommitReport{Id: response.ID}, nil } func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrID string, options entities.ContainerExportOptions) error { var ( err error w io.Writer ) if len(options.Output) > 0 { w, err = os.Create(options.Output) if err != nil { return err } } return containers.Export(ic.ClientCtx, nameOrID, w, nil) } func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, opts entities.CheckpointOptions) ([]*entities.CheckpointReport, error) { var ( err error ctrs []entities.ListContainer rawInputs []string idToRawInput = map[string]string{} ) options := new(containers.CheckpointOptions) options.WithFileLocks(opts.FileLocks) options.WithIgnoreRootfs(opts.IgnoreRootFS) options.WithKeep(opts.Keep) options.WithExport(opts.Export) options.WithCreateImage(opts.CreateImage) options.WithTCPEstablished(opts.TCPEstablished) options.WithPrintStats(opts.PrintStats) options.WithPreCheckpoint(opts.PreCheckPoint) options.WithLeaveRunning(opts.LeaveRunning) options.WithWithPrevious(opts.WithPrevious) if opts.All { allCtrs, err := getContainersByContext(ic.ClientCtx, true, false, []string{}) if err != nil { return nil, err } // narrow the list to running only for _, c := range allCtrs { if c.State == define.ContainerStateRunning.String() { ctrs = append(ctrs, c) } } } else { ctrs, rawInputs, err = getContainersAndInputByContext(ic.ClientCtx, false, false, namesOrIds, nil) if err != nil { return nil, err } if len(rawInputs) == len(ctrs) { for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } } } reports := make([]*entities.CheckpointReport, 0, len(ctrs)) for _, c := range ctrs { report, err := containers.Checkpoint(ic.ClientCtx, c.ID, options) if err != nil { reports = append(reports, &entities.CheckpointReport{Id: c.ID, Err: err}) } else { report.RawInput = idToRawInput[report.Id] reports = append(reports, report) } } return reports, nil } func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, opts entities.RestoreOptions) ([]*entities.RestoreReport, error) { if opts.ImportPrevious != "" { return nil, fmt.Errorf("--import-previous is not supported on the remote client") } var ( ids []string idToRawInput = map[string]string{} ) options := new(containers.RestoreOptions) options.WithFileLocks(opts.FileLocks) options.WithIgnoreRootfs(opts.IgnoreRootFS) options.WithIgnoreVolumes(opts.IgnoreVolumes) options.WithIgnoreStaticIP(opts.IgnoreStaticIP) options.WithIgnoreStaticMAC(opts.IgnoreStaticMAC) options.WithKeep(opts.Keep) options.WithName(opts.Name) options.WithTCPEstablished(opts.TCPEstablished) options.WithPod(opts.Pod) options.WithPrintStats(opts.PrintStats) options.WithPublishPorts(opts.PublishPorts) if opts.Import != "" { options.WithImportArchive(opts.Import) report, err := containers.Restore(ic.ClientCtx, "", options) return []*entities.RestoreReport{report}, err } if opts.All { allCtrs, err := getContainersByContext(ic.ClientCtx, true, false, []string{}) if err != nil { return nil, err } // narrow the list to exited only for _, c := range allCtrs { if c.State == define.ContainerStateExited.String() { ids = append(ids, c.ID) } } } else { getImageOptions := new(images.GetOptions).WithSize(false) hostInfo, err := ic.Info(context.Background()) if err != nil { return nil, err } for _, nameOrID := range namesOrIds { ctrData, _, err := ic.ContainerInspect(ic.ClientCtx, []string{nameOrID}, entities.InspectOptions{}) if err == nil && len(ctrData) > 0 { ids = append(ids, ctrData[0].ID) idToRawInput[ctrData[0].ID] = nameOrID } else { // If container was not found, check if this is a checkpoint image inspectReport, err := images.GetImage(ic.ClientCtx, nameOrID, getImageOptions) if err != nil { return nil, fmt.Errorf("no such container or image: %s", nameOrID) } checkpointRuntimeName, found := inspectReport.Annotations[define.CheckpointAnnotationRuntimeName] if !found { return nil, fmt.Errorf("image is not a checkpoint: %s", nameOrID) } if hostInfo.Host.OCIRuntime.Name != checkpointRuntimeName { return nil, fmt.Errorf("container image \"%s\" requires runtime: \"%s\"", nameOrID, checkpointRuntimeName) } ids = append(ids, inspectReport.ID) } } } reports := make([]*entities.RestoreReport, 0, len(ids)) for _, id := range ids { report, err := containers.Restore(ic.ClientCtx, id, options) if err != nil { reports = append(reports, &entities.RestoreReport{Id: id, Err: err}) } report.RawInput = idToRawInput[report.Id] reports = append(reports, report) } return reports, nil } func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) { response, err := containers.CreateWithSpec(ic.ClientCtx, s, nil) if err != nil { return nil, err } for _, w := range response.Warnings { fmt.Fprintf(os.Stderr, "%s\n", w) } return &entities.ContainerCreateReport{Id: response.ID}, nil } func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, opts entities.ContainerLogsOptions) error { since := opts.Since.Format(time.RFC3339) until := opts.Until.Format(time.RFC3339) tail := strconv.FormatInt(opts.Tail, 10) stdout := opts.StdoutWriter != nil stderr := opts.StderrWriter != nil options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithUntil(until).WithStderr(stderr) options.WithStdout(stdout).WithTail(tail) var err error stdoutCh := make(chan string) stderrCh := make(chan string) ctx, cancel := context.WithCancel(context.Background()) go func() { err = containers.Logs(ic.ClientCtx, nameOrIDs[0], options, stdoutCh, stderrCh) cancel() }() for { select { case <-ctx.Done(): return err case line := <-stdoutCh: if opts.StdoutWriter != nil { _, _ = io.WriteString(opts.StdoutWriter, line) } case line := <-stderrCh: if opts.StderrWriter != nil { _, _ = io.WriteString(opts.StderrWriter, line) } } } } func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrID string, opts entities.AttachOptions) error { ctrs, err := getContainersByContext(ic.ClientCtx, false, false, []string{nameOrID}) if err != nil { return err } ctr := ctrs[0] if ctr.State != define.ContainerStateRunning.String() { return fmt.Errorf("you can only attach to running containers") } options := new(containers.AttachOptions).WithStream(true).WithDetachKeys(opts.DetachKeys) return containers.Attach(ic.ClientCtx, nameOrID, opts.Stdin, opts.Stdout, opts.Stderr, nil, options) } func makeExecConfig(options entities.ExecOptions) *handlers.ExecCreateConfig { env := []string{} for k, v := range options.Envs { env = append(env, fmt.Sprintf("%s=%s", k, v)) } createConfig := new(handlers.ExecCreateConfig) createConfig.User = options.User createConfig.Privileged = options.Privileged createConfig.Tty = options.Tty createConfig.AttachStdin = options.Interactive createConfig.AttachStdout = true createConfig.AttachStderr = true createConfig.Detach = false createConfig.DetachKeys = options.DetachKeys createConfig.Env = env createConfig.WorkingDir = options.WorkDir createConfig.Cmd = options.Cmd return createConfig } func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, options entities.ExecOptions, streams define.AttachStreams) (int, error) { createConfig := makeExecConfig(options) sessionID, err := containers.ExecCreate(ic.ClientCtx, nameOrID, createConfig) if err != nil { return 125, err } startAndAttachOptions := new(containers.ExecStartAndAttachOptions) startAndAttachOptions.WithOutputStream(streams.OutputStream).WithErrorStream(streams.ErrorStream) if streams.InputStream != nil { startAndAttachOptions.WithInputStream(*streams.InputStream) } startAndAttachOptions.WithAttachError(streams.AttachError).WithAttachOutput(streams.AttachOutput).WithAttachInput(streams.AttachInput) if err := containers.ExecStartAndAttach(ic.ClientCtx, sessionID, startAndAttachOptions); err != nil { return 125, err } inspectOut, err := containers.ExecInspect(ic.ClientCtx, sessionID, nil) if err != nil { return 125, err } return inspectOut.ExitCode, nil } func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (string, error) { createConfig := makeExecConfig(options) sessionID, err := containers.ExecCreate(ic.ClientCtx, nameOrID, createConfig) if err != nil { return "", err } if err := containers.ExecStart(ic.ClientCtx, sessionID, nil); err != nil { return "", err } return sessionID, nil } func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { attachErr := make(chan error) attachReady := make(chan bool) options := new(containers.AttachOptions).WithStream(true) if dk := detachKeys; dk != nil { options.WithDetachKeys(*dk) } go func() { err := containers.Attach(ic.ClientCtx, name, input, output, errput, attachReady, options) attachErr <- err }() // Wait for the attach to actually happen before starting // the container. select { case <-attachReady: startOptions := new(containers.StartOptions) if dk := detachKeys; dk != nil { startOptions.WithDetachKeys(*dk) } if err := containers.Start(ic.ClientCtx, name, startOptions); err != nil { return err } case err := <-attachErr: return err } // If attachReady happens first, wait for containers.Attach to complete return <-attachErr } func logIfRmError(id string, err error, reports []*reports.RmReport) { logError := func(id string, err error) { if errorhandling.Contains(err, define.ErrNoSuchCtr) || errorhandling.Contains(err, define.ErrCtrRemoved) || errorhandling.Contains(err, types.ErrLayerUnknown) { logrus.Debugf("Container %s does not exist: %v", id, err) } else { logrus.Errorf("Removing container %s: %v", id, err) } } if err != nil { logError(id, err) } else { for _, report := range reports { if report.Err != nil { logError(report.Id, report.Err) } } } } func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { reports := []*entities.ContainerStartReport{} var exitCode = define.ExecErrorCodeGeneric ctrs, namesOrIds, err := getContainersAndInputByContext(ic.ClientCtx, options.All, false, namesOrIds, options.Filters) if err != nil { return nil, err } removeOptions := new(containers.RemoveOptions).WithVolumes(true).WithForce(false) removeContainer := func(id string) { reports, err := containers.Remove(ic.ClientCtx, id, removeOptions) logIfRmError(id, err, reports) } // There can only be one container if attach was used for i, ctr := range ctrs { name := ctr.ID rawInput := ctr.ID if !options.All { rawInput = namesOrIds[i] } report := entities.ContainerStartReport{ Id: name, RawInput: rawInput, ExitCode: exitCode, } ctrRunning := ctr.State == define.ContainerStateRunning.String() if options.Attach { err = startAndAttach(ic, name, &options.DetachKeys, options.Stdin, options.Stdout, options.Stderr) if err == define.ErrDetach { // User manually detached // Exit cleanly immediately reports = append(reports, &report) return reports, nil } if ctrRunning { reports = append(reports, &report) return reports, nil } if err != nil { if ctr.AutoRemove { removeContainer(ctr.ID) } report.ExitCode = define.ExitCode(report.Err) report.Err = err reports = append(reports, &report) return reports, fmt.Errorf("unable to start container %s: %w", name, report.Err) } if ctr.AutoRemove { // Defer the removal, so we can return early if needed and // de-spaghetti the code. defer func() { shouldRestart, err := containers.ShouldRestart(ic.ClientCtx, ctr.ID, nil) if err != nil { logrus.Errorf("Failed to check if %s should restart: %v", ctr.ID, err) return } logrus.Errorf("Should restart: %v", shouldRestart) if !shouldRestart && ctr.AutoRemove { removeContainer(ctr.ID) } }() } exitCode, err := containers.Wait(ic.ClientCtx, name, nil) if err == define.ErrNoSuchCtr { // Check events event, err := ic.GetLastContainerEvent(ctx, name, events.Exited) if err != nil { logrus.Errorf("Cannot get exit code: %v", err) report.ExitCode = define.ExecErrorCodeNotFound } else { report.ExitCode = event.ContainerExitCode } } else { report.ExitCode = int(exitCode) } reports = append(reports, &report) return reports, nil } // Start the container if it's not running already. if !ctrRunning { err = containers.Start(ic.ClientCtx, name, new(containers.StartOptions).WithDetachKeys(options.DetachKeys)) if err != nil { if ctr.AutoRemove { rmOptions := new(containers.RemoveOptions).WithForce(false).WithVolumes(true) reports, err := containers.Remove(ic.ClientCtx, ctr.ID, rmOptions) logIfRmError(ctr.ID, err, reports) } report.Err = fmt.Errorf("unable to start container %q: %w", name, err) report.ExitCode = define.ExitCode(err) reports = append(reports, &report) continue } report.ExitCode = 0 reports = append(reports, &report) } } return reports, nil } func (ic *ContainerEngine) ContainerList(ctx context.Context, opts entities.ContainerListOptions) ([]entities.ListContainer, error) { options := new(containers.ListOptions).WithFilters(opts.Filters).WithAll(opts.All).WithLast(opts.Last) options.WithNamespace(opts.Namespace).WithSize(opts.Size).WithSync(opts.Sync).WithExternal(opts.External) return containers.List(ic.ClientCtx, options) } func (ic *ContainerEngine) ContainerListExternal(ctx context.Context) ([]entities.ListContainer, error) { options := new(containers.ListOptions).WithAll(true) options.WithNamespace(true).WithSize(true).WithSync(true).WithExternal(true) return containers.List(ic.ClientCtx, options) } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { con, err := containers.CreateWithSpec(ic.ClientCtx, opts.Spec, nil) if err != nil { return nil, err } for _, w := range con.Warnings { fmt.Fprintf(os.Stderr, "%s\n", w) } removeContainer := func(id string, force bool) error { removeOptions := new(containers.RemoveOptions).WithVolumes(true).WithForce(force) reports, err := containers.Remove(ic.ClientCtx, id, removeOptions) logIfRmError(id, err, reports) return err } if opts.CIDFile != "" { if err := util.CreateCidFile(opts.CIDFile, con.ID); err != nil { // If you fail to create CIDFile then remove the container _ = removeContainer(con.ID, true) return nil, err } } report := entities.ContainerRunReport{Id: con.ID} if opts.Detach { // Detach and return early err := containers.Start(ic.ClientCtx, con.ID, new(containers.StartOptions).WithRecursive(true)) if err != nil { report.ExitCode = define.ExitCode(err) if opts.Rm { if rmErr := removeContainer(con.ID, true); rmErr != nil && !errors.Is(rmErr, define.ErrNoSuchCtr) { logrus.Errorf("Container %s failed to be removed", con.ID) } } } return &report, err } // Attach if err := startAndAttach(ic, con.ID, &opts.DetachKeys, opts.InputStream, opts.OutputStream, opts.ErrorStream); err != nil { if err == define.ErrDetach { return &report, nil } report.ExitCode = define.ExitCode(err) if opts.Rm { _ = removeContainer(con.ID, false) } return &report, err } if opts.Rm { // Defer the removal, so we can return early if needed and // de-spaghetti the code. defer func() { shouldRestart, err := containers.ShouldRestart(ic.ClientCtx, con.ID, nil) if err != nil { logrus.Errorf("Failed to check if %s should restart: %v", con.ID, err) return } if !shouldRestart { _ = removeContainer(con.ID, false) } }() } // Wait exitCode, waitErr := containers.Wait(ic.ClientCtx, con.ID, nil) if waitErr == nil { report.ExitCode = int(exitCode) return &report, nil } // Determine why the wait failed. If the container doesn't exist, // consult the events. if !errorhandling.Contains(waitErr, define.ErrNoSuchCtr) { return &report, waitErr } // Events eventsChannel := make(chan *events.Event) eventOptions := entities.EventsOptions{ EventChan: eventsChannel, Filter: []string{ "type=container", fmt.Sprintf("container=%s", con.ID), fmt.Sprintf("event=%s", events.Exited), }, } var lastEvent *events.Event var mutex sync.Mutex mutex.Lock() // Read the events. go func() { for e := range eventsChannel { lastEvent = e } mutex.Unlock() }() eventsErr := ic.Events(ctx, eventOptions) // Wait for all events to be read mutex.Lock() if eventsErr != nil || lastEvent == nil { logrus.Errorf("Cannot get exit code: %v", err) report.ExitCode = define.ExecErrorCodeNotFound return &report, nil //nolint: nilerr } report.ExitCode = lastEvent.ContainerExitCode return &report, err } func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts entities.DiffOptions) (*entities.DiffReport, error) { var base string options := new(containers.DiffOptions).WithDiffType(opts.Type.String()) if len(namesOrIDs) > 0 { base = namesOrIDs[0] if len(namesOrIDs) > 1 { options.WithParent(namesOrIDs[1]) } } else { return nil, errors.New("no arguments for diff") } changes, err := containers.Diff(ic.ClientCtx, base, options) return &entities.DiffReport{Changes: changes}, err } func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []string, options entities.ContainerCleanupOptions) ([]*entities.ContainerCleanupReport, error) { return nil, errors.New("not implemented") } func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []string, options entities.ContainerInitOptions) ([]*entities.ContainerInitReport, error) { ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, options.All, false, namesOrIds, nil) if err != nil { return nil, err } idToRawInput := map[string]string{} if len(rawInputs) == len(ctrs) { for i := range ctrs { idToRawInput[ctrs[i].ID] = rawInputs[i] } } reports := make([]*entities.ContainerInitReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.ContainerInit(ic.ClientCtx, c.ID, nil) // When using all, it is NOT considered an error if a container // has already been init'd. if err != nil && options.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { err = nil } reports = append(reports, &entities.ContainerInitReport{ Err: err, RawInput: idToRawInput[c.ID], Id: c.ID, }) } return reports, nil } func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIDs []string, options entities.ContainerMountOptions) ([]*entities.ContainerMountReport, error) { return nil, errors.New("mounting containers is not supported for remote clients") } func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIDs []string, options entities.ContainerUnmountOptions) ([]*entities.ContainerUnmountReport, error) { return nil, errors.New("unmounting containers is not supported for remote clients") } func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { return config.Default() } func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) { var ( reports = []*entities.ContainerPortReport{} namesOrIds = []string{} ) if len(nameOrID) > 0 { namesOrIds = append(namesOrIds, nameOrID) } ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) if err != nil { return nil, err } for _, con := range ctrs { if con.State != define.ContainerStateRunning.String() { continue } if len(con.Ports) > 0 { reports = append(reports, &entities.ContainerPortReport{ Id: con.ID, Ports: con.Ports, }) } } return reports, nil } func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) { copyOptions := new(containers.CopyOptions).WithChown(options.Chown).WithRename(options.Rename).WithNoOverwriteDirNonDir(options.NoOverwriteDirNonDir) return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, copyOptions) } func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) { return containers.CopyToArchive(ic.ClientCtx, nameOrID, path, writer) } func (ic *ContainerEngine) ContainerStat(ctx context.Context, nameOrID string, path string) (*entities.ContainerStatReport, error) { return containers.Stat(ic.ClientCtx, nameOrID, path) } // Shutdown Libpod engine. func (ic *ContainerEngine) Shutdown(_ context.Context) { } func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { if options.Latest { return nil, errors.New("latest is not supported for the remote client") } return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream).WithInterval(options.Interval)) } // ShouldRestart reports back whether the container will restart. func (ic *ContainerEngine) ShouldRestart(_ context.Context, id string) (bool, error) { return containers.ShouldRestart(ic.ClientCtx, id, nil) } // ContainerRename renames the given container. func (ic *ContainerEngine) ContainerRename(ctx context.Context, nameOrID string, opts entities.ContainerRenameOptions) error { return containers.Rename(ic.ClientCtx, nameOrID, new(containers.RenameOptions).WithName(opts.NewName)) } func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts entities.ContainerCloneOptions) (*entities.ContainerCreateReport, error) { return nil, errors.New("cloning a container is not supported on the remote client") }