Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support State.Error field in inspect command #2073

Merged
merged 1 commit into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion cmd/nerdctl/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func newCreateCommand() *cobra.Command {
return createCommand
}

func createAction(cmd *cobra.Command, args []string) error {
func createAction(cmd *cobra.Command, args []string) (err error) {
globalOptions, err := processRootCmdFlags(cmd)
if err != nil {
return err
Expand Down Expand Up @@ -91,6 +91,13 @@ func createAction(cmd *cobra.Command, args []string) error {
}
return err
}
// defer setting `nerdctl/error` label in case of error
defer func() {
if err != nil {
containerutil.UpdateErrorLabel(ctx, container, err)
}
}()

fmt.Fprintln(cmd.OutOrStdout(), container.ID())
return nil
}
54 changes: 54 additions & 0 deletions cmd/nerdctl/container_inspect_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"fmt"
"strings"
"testing"

"github.com/containerd/nerdctl/pkg/inspecttypes/dockercompat"
Expand Down Expand Up @@ -154,3 +155,56 @@ func TestContainerInspectContainsLabel(t *testing.T) {
assert.Equal(base.T, "foo", lbs["foo"])
assert.Equal(base.T, "bar", lbs["bar"])
}

func TestContainerInspectState(t *testing.T) {
t.Parallel()
testContainer := testutil.Identifier(t)
base := testutil.NewBase(t)

type testCase struct {
name, containerName, cmd string
want dockercompat.ContainerState
}
// nerdctl: run error produces a nil Task, so the Status is empty because Status comes from Task.
// docker : run error gives => `Status=created` as in docker there is no a separation between container and Task.
errStatus := ""
if base.Target == testutil.Docker {
errStatus = "created"
fahedouch marked this conversation as resolved.
Show resolved Hide resolved
}
testCases := []testCase{
{
name: "inspect State with error",
containerName: fmt.Sprintf("%s-fail", testContainer),
cmd: "aa",
want: dockercompat.ContainerState{
Error: "executable file not found in $PATH",
Status: errStatus,
},
},
{
name: "inspect State without error",
containerName: fmt.Sprintf("%s-success", testContainer),
cmd: "ls",
want: dockercompat.ContainerState{
Error: "",
Status: "exited",
},
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
defer base.Cmd("rm", "-f", tc.containerName).Run()
if tc.want.Error != "" {
base.Cmd("run", "--name", tc.containerName, testutil.AlpineImage, tc.cmd).AssertFail()
} else {
base.Cmd("run", "--name", tc.containerName, testutil.AlpineImage, tc.cmd).AssertOK()
}
inspect := base.InspectContainer(tc.containerName)
assert.Assert(t, strings.Contains(inspect.State.Error, tc.want.Error), fmt.Sprintf("expected: %s, actual: %s", tc.want.Error, inspect.State.Error))
assert.Equal(base.T, inspect.State.Status, tc.want.Status)
})
}

}
8 changes: 7 additions & 1 deletion cmd/nerdctl/container_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func setCreateFlags(cmd *cobra.Command) {

// runAction is heavily based on ctr implementation:
// https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/run/run.go
func runAction(cmd *cobra.Command, args []string) error {
func runAction(cmd *cobra.Command, args []string) (err error) {
globalOptions, err := processRootCmdFlags(cmd)
if err != nil {
return err
Expand Down Expand Up @@ -346,6 +346,12 @@ func runAction(cmd *cobra.Command, args []string) error {
}
return err
}
// defer setting `nerdctl/error` label in case of error
defer func() {
if err != nil {
containerutil.UpdateErrorLabel(ctx, c, err)
}
}()

id := c.ID()
if rm && !flagD {
Expand Down
6 changes: 6 additions & 0 deletions pkg/cmd/container/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ func Remove(ctx context.Context, client *containerd.Client, containers []string,

// RemoveContainer removes a container from containerd store.
func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions types.GlobalCommandOptions, force bool, removeAnonVolumes bool) (retErr error) {
// defer the storage of remove error in the dedicated label
defer func() {
if retErr != nil {
containerutil.UpdateErrorLabel(ctx, c, retErr)
}
}()
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
Expand Down
25 changes: 23 additions & 2 deletions pkg/containerutil/containerutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ func UpdateExplicitlyStoppedLabel(ctx context.Context, container containerd.Cont
return container.Update(ctx, containerd.UpdateContainerOpts(opt))
}

// UpdateErrorLabel updates the "nerdctl/error"
// label of the container according to the container error.
func UpdateErrorLabel(ctx context.Context, container containerd.Container, err error) error {
opt := containerd.WithAdditionalContainerLabels(map[string]string{
labels.Error: err.Error(),
})
return container.Update(ctx, containerd.UpdateContainerOpts(opt))
}

// WithBindMountHostProcfs replaces procfs mount with rbind.
// Required for --pid=host on rootless.
//
Expand Down Expand Up @@ -189,7 +198,13 @@ func GenerateSharingPIDOpts(ctx context.Context, targetCon containerd.Container)
}

// Start starts `container` with `attach` flag. If `attach` is true, it will attach to the container's stdio.
func Start(ctx context.Context, container containerd.Container, flagA bool, client *containerd.Client) error {
func Start(ctx context.Context, container containerd.Container, flagA bool, client *containerd.Client) (err error) {
// defer the storage of start error in the dedicated label
defer func() {
if err != nil {
UpdateErrorLabel(ctx, container, err)
}
}()
lab, err := container.Labels(ctx)
if err != nil {
return err
Expand Down Expand Up @@ -272,7 +287,13 @@ func Start(ctx context.Context, container containerd.Container, flagA bool, clie
}

// Stop stops `container` by sending SIGTERM. If the container is not stopped after `timeout`, it sends a SIGKILL.
func Stop(ctx context.Context, container containerd.Container, timeout *time.Duration) error {
func Stop(ctx context.Context, container containerd.Container, timeout *time.Duration) (err error) {
// defer the storage of stop error in the dedicated label
defer func() {
if err != nil {
UpdateErrorLabel(ctx, container, err)
}
}()
if err := UpdateExplicitlyStoppedLabel(ctx, container, true); err != nil {
return err
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/errutil/exit_coder.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ type ExitCoder interface {

// ExitCodeError is to allow the program to exit with status code without outputting an error message.
type ExitCodeError struct {
error
exitCode int
}

Expand All @@ -39,6 +38,10 @@ func (e ExitCodeError) ExitCode() int {
return e.exitCode
}

func (e ExitCodeError) Error() string {
return ""
}

func HandleExitCoder(err error) {
if err == nil {
return
Expand Down
22 changes: 11 additions & 11 deletions pkg/inspecttypes/dockercompat/dockercompat.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ type ContainerState struct {
// TODO: Dead bool
Pid int
ExitCode int
// TODO: Error string
Error string
// TODO: StartedAt string
FinishedAt string
// TODO: Health *Health `json:",omitempty"`
Expand Down Expand Up @@ -257,23 +257,23 @@ func ContainerFromNative(n *native.Container) (*Container, error) {
c.Mounts = mounts
}

cs := new(ContainerState)
cs.Restarting = n.Labels[restart.StatusLabel] == string(containerd.Running)
cs.Error = n.Labels[labels.Error]
if n.Process != nil {
c.State = &ContainerState{
Status: statusFromNative(n.Process.Status, n.Labels),
Running: n.Process.Status.Status == containerd.Running,
Paused: n.Process.Status.Status == containerd.Paused,
Restarting: n.Labels[restart.StatusLabel] == string(containerd.Running),
Pid: n.Process.Pid,
ExitCode: int(n.Process.Status.ExitStatus),
FinishedAt: n.Process.Status.ExitTime.Format(time.RFC3339Nano),
}
cs.Status = statusFromNative(n.Process.Status, n.Labels)
cs.Running = n.Process.Status.Status == containerd.Running
cs.Paused = n.Process.Status.Status == containerd.Paused
cs.Pid = n.Process.Pid
cs.ExitCode = int(n.Process.Status.ExitStatus)
cs.FinishedAt = n.Process.Status.ExitTime.Format(time.RFC3339Nano)
nSettings, err := networkSettingsFromNative(n.Process.NetNS, n.Spec.(*specs.Spec))
if err != nil {
return nil, err
}
c.NetworkSettings = nSettings
}

c.State = cs
c.Config = &Config{
Hostname: n.Labels[labels.Hostname],
Labels: n.Labels,
Expand Down
5 changes: 4 additions & 1 deletion pkg/labels/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const (
Ports = Prefix + "ports"

// IPAddress is the static IP address of the container assigned by the user

IPAddress = Prefix + "ip"

// LogURI is the log URI
Expand Down Expand Up @@ -90,6 +89,10 @@ const (
// PIDContainer is the `nerdctl run --pid` for restarting
PIDContainer = Prefix + "pid-container"

// Error encapsulates a container human-readable string
// that describes container error.
Error = Prefix + "error"

// NerdctlDefaultNetwork indicates whether a network is the default network
// created and owned by Nerdctl.
// Boolean value which can be parsed with strconv.ParseBool() is required.
Expand Down