container

package
v0.0.0-...-32a30f5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 31, 2026 License: Apache-2.0, BSD-2-Clause Imports: 34 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Command = &cli.Command{
	Name:    "container",
	Aliases: []string{"c", "ctr"},
	Usage:   "manage cubebox",
	Subcommands: []*cli.Command{
		ListCommand,
		InfoCommand,
		ExecCommand,
		ListTapCommand,
	},
}
View Source
var ExecCommand = &cli.Command{
	Name:                   "exec",
	Usage:                  "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
	UseShortOptionHandling: true,
	ArgsUsage:              "[flags] CONTAINER COMMAND [ARG...]",
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:    "tty",
			Aliases: []string{"t"},
			Usage:   "(Currently -t needs to correspond to -i)",
		},
		&cli.BoolFlag{
			Name:    "interactive",
			Aliases: []string{"i"},
			Usage:   "Keep STDIN open even if not attached",
		},
		&cli.StringFlag{
			Name:    "workdir",
			Aliases: []string{"w"},
			Usage:   "Working directory inside the container",
		},

		&cli.BoolFlag{
			Name:    "detach",
			Aliases: []string{"d"},
			Usage:   "Detached mode: run command in the background",
		},
	},
	Action: func(context *cli.Context) error {
		var (
			args = context.Args()
		)
		if args.Len() < 2 {
			return fmt.Errorf("requires at least %d arg(s), only received %d", 2, args.Len())
		}

		newArg := []string{}
		if args.Len() >= 2 {
			if args.Get(1) == "--" {
				newArg = append(newArg, args.Slice()[:1]...)
				newArg = append(newArg, args.Slice()[2:]...)
			} else {
				newArg = args.Slice()
			}
		}

		cntdClient, err := containerd.New(context.String("address"),
			containerd.WithDefaultPlatform(platforms.Default()))
		if err != nil {
			return fmt.Errorf("init containerd connect failed.%s", err)
		}
		cntCtx := namespaces.WithNamespace(gocontext.Background(), context.String("namespace"))

		containerIDKey := newArg[0]
		containers, err := findContainer(cntCtx, containerIDKey, cntdClient)
		if err != nil {
			return err
		}
		if len(containers) == 0 {
			containerIDKey, err = findCubeboxContainer(context, containerIDKey)
			if err != nil {
				return err
			}
			containers, err = findContainer(cntCtx, containerIDKey, cntdClient)
			if err != nil {
				return err
			}
			if len(containers) == 0 {
				return fmt.Errorf("no such container %s", containerIDKey)
			}
		}

		flagI := context.Bool("interactive")
		flagT := context.Bool("tty")
		flagD := context.Bool("detach")

		if flagI {
			if flagD {
				return errors.New("currently flag -i and -d cannot be specified together (FIXME)")
			}
		}

		if flagT {
			if flagD {
				return errors.New("currently flag -t and -d cannot be specified together (FIXME)")
			}
		}

		container := containers[0]
		pspec, err := generateExecProcessSpec(cntCtx, context, newArg, container, cntdClient)
		if err != nil {
			return fmt.Errorf("failed to generate exec process spec: %w", err)
		}

		task, err := container.Task(cntCtx, nil)
		if err != nil {
			return fmt.Errorf("failed to get task: %w", err)
		}
		var (
			ioCreator cio.Creator
			stdinC    = newStdinCloser(os.Stdin)
			con       console.Console
		)

		cioOpts := []cio.Opt{cio.WithFIFODir("/data/cubelet/fifo")}
		if flagT {
			con = console.Current()
			defer con.Reset()
			if err := con.SetRaw(); err != nil {
				return err
			}
			cioOpts = append(cioOpts, cio.WithStreams(con, con, nil), cio.WithTerminal, cio.WithFIFODir("/data/cubelet/fifo"))
		} else {
			cioOpts = append(cioOpts, cio.WithStreams(stdinC, os.Stdout, os.Stderr))
		}
		ioCreator = cio.NewCreator(cioOpts...)

		execID := "exec-" + utils.GenerateID()
		process, err := task.Exec(cntCtx, execID, pspec, ioCreator)
		if err != nil {
			return fmt.Errorf("failed to exec: %w", err)
		}
		stdinC.SetCloser(func() {
			process.CloseIO(cntCtx, containerd.WithStdinCloser)
		})

		if !flagD {
			defer process.Delete(cntCtx)
		}

		statusC, err := process.Wait(cntCtx)
		if err != nil {
			return fmt.Errorf("failed to get wait channel: %w", err)
		}

		if !flagD {
			if flagT {
				if err := tasks.HandleConsoleResize(cntCtx, process, con); err != nil {
					logrus.WithError(err).Error("console resize")
				}
			} else {
				sigc := commands.ForwardAllSignals(cntCtx, process)
				defer commands.StopCatch(sigc)
			}
		}

		if err := process.Start(cntCtx); err != nil {
			return err
		}
		if flagD {
			return nil
		}
		status := <-statusC
		code, _, err := status.Result()
		if err != nil {
			return fmt.Errorf("failed to get exit code: %w", err)
		}
		if code != 0 {
			return fmt.Errorf("exec failed with exit code %d", code)
		}
		return nil
	},
}
View Source
var InfoCommand = &cli.Command{
	Name:      "info",
	Usage:     "get info about a container",
	ArgsUsage: "CONTAINER",
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:  "spec",
			Usage: "only display the spec",
		},
	},
	Action: func(context *cli.Context) error {
		id := context.Args().First()
		if id == "" {
			return fmt.Errorf("container id must be provided: %w", errdefs.ErrInvalidArgument)
		}
		cntdClient, err := containerd.New(context.String("address"),
			containerd.WithDefaultPlatform(platforms.Default()),
		)
		if err != nil {
			return err
		}
		cntCtx := namespaces.WithNamespace(gocontext.Background(), context.String("namespace"))
		cntCtx, cntCancel := gocontext.WithTimeout(cntCtx, context.Duration("timeout"))
		defer cntCancel()
		filters := []string{
			fmt.Sprintf("id~=^%s.*$", regexp.QuoteMeta(id)),
		}

		cntrs, err := cntdClient.Containers(cntCtx, filters...)
		if err != nil {
			return err
		}
		for _, c := range cntrs {
			info, err := c.Info(cntCtx, containerd.WithoutRefreshedMetadata)
			if err != nil {
				if errdefs.IsNotFound(err) {
					log.L.WithError(err).Error("container not found")
					continue
				}
				return err
			}
			if context.Bool("spec") {
				v, err := typeurl.UnmarshalAny(info.Spec)
				if err != nil {
					fmt.Printf("failed to unmarshal container spec with url [%s]: %s", info.Spec.GetTypeUrl(), string(info.Spec.GetValue()))
					return err
				}
				commands.PrintAsJSON(v)
				return nil
			}

			if info.Spec != nil && info.Spec.GetValue() != nil {
				v, err := typeurl.UnmarshalAny(info.Spec)
				if err != nil {
					return fmt.Errorf("failed to unmarshal container spec with url %s: %w", info.Spec.GetTypeUrl(), err)
				}
				commands.PrintAsJSON(struct {
					containers.Container
					Spec interface{} `json:"Spec,omitempty"`
				}{
					Container: info,
					Spec:      v,
				})
				return nil
			}
			commands.PrintAsJSON(info)
		}

		return nil
	},
}
View Source
var ListCommand = &cli.Command{
	Name:    "list",
	Aliases: []string{"ls"},
	Usage:   "list containers",
	ArgsUsage: "[flags] [<filter>, ...]\n" +
		"io.kubernetes.cri.container-type [container|sandbox]\n" +
		"io.kubernetes.cri.sandbox-id xx",
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:    "quiet",
			Aliases: []string{"q"},
			Usage:   "print only the container id",
		},
		&cli.BoolFlag{
			Name:    "all",
			Aliases: []string{"a"},
			Usage:   "Show all containers (default shows just running)",
		},
		&cli.IntFlag{
			Name:    "last",
			Aliases: []string{"n"},
			Usage:   "Show n last created containers (includes all states)",
		},
		&cli.BoolFlag{
			Name:    "latest",
			Aliases: []string{"l"},
			Usage:   "Show the latest created container (includes all states)",
		},
		&cli.BoolFlag{
			Name:  "no-trunc",
			Usage: "Don't truncate output",
		},
		&cli.StringFlag{
			Name:    "sandbox",
			Aliases: []string{"s"},
			Usage:   "filter sandbox",
		},
	},
	Action: func(context *cli.Context) error {
		var (
			args    = context.Args()
			quiet   = context.Bool("quiet")
			filters []string
		)
		cntdClient, err := containerd.New(context.String("address"),
			containerd.WithDefaultPlatform(platforms.Default()),
		)
		if err != nil {
			return err
		}
		cntCtx := namespaces.WithNamespace(gocontext.Background(), context.String("namespace"))
		cntCtx, cntCancel := gocontext.WithTimeout(cntCtx, context.Duration("timeout"))
		defer cntCancel()

		if args.Len() == 1 {
			filters = []string{
				fmt.Sprintf("id~=^%s.*$", regexp.QuoteMeta(args.Get(0))),
			}
		}

		if args.Len() == 2 {
			filters = append(filters, fmt.Sprintf("labels.%q==%s", args.Get(0), args.Get(1)))
		}

		if context.IsSet("sandbox") {
			filters = append(filters, fmt.Sprintf("labels.\"io.kubernetes.cri.sandbox-id\"==%s",
				context.String("sandbox")))
		}

		containers, err := cntdClient.Containers(cntCtx, filters...)
		if err != nil {
			return err
		}
		trunc := !context.Bool("no-trunc")
		all := context.Bool("all")
		latest := context.Bool("latest")
		lastN := context.Int("last")
		withoutSandbox := context.Bool("without-sandbox-id")
		if lastN == -1 && latest {
			lastN = 1
		}
		if !all && lastN > 0 {
			all = true
			sort.Slice(containers, func(i, j int) bool {
				infoI, _ := containers[i].Info(cntCtx, containerd.WithoutRefreshedMetadata)
				infoJ, _ := containers[j].Info(cntCtx, containerd.WithoutRefreshedMetadata)
				return infoI.CreatedAt.After(infoJ.CreatedAt)
			})
			if lastN < len(containers) {
				containers = containers[:lastN]
			}
		}

		if quiet {
			for _, c := range containers {
				fmt.Printf("%s\n", c.ID())
			}
			return nil
		}

		w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
		title := "CONTAINER"
		if !withoutSandbox {
			title += "\tSANDBOX"
		}
		title += "\tTYPE\tIMAGE\tSTATUS\tCREATED"

		fmt.Fprintln(w, title)
		for _, c := range containers {
			info, err := c.Info(cntCtx, containerd.WithoutRefreshedMetadata)
			if err != nil {
				if errdefs.IsNotFound(err) {
					log.L.WithError(err).Error("container not found")
					continue
				}
				return err
			}

			imageName := info.Image
			if imageName == "" {
				imageName = "-"
			}
			sandboxID := info.SandboxID
			if trunc && len(sandboxID) > 12 {
				sandboxID = sandboxID[:12]
			}
			id := c.ID()
			if trunc && len(id) > 12 {
				id = id[:12]
			}
			cStatus := ContainerStatus(cntCtx, c)
			ctype := getContainerType(info.Labels)
			if _, err := fmt.Fprintf(w, "%s", id); err != nil {
				return err
			}
			if !withoutSandbox {
				if _, err := fmt.Fprintf(w, "\t%s", sandboxID); err != nil {
					return err
				}
			}
			if _, err := fmt.Fprintf(w, "\t%s\t%s\t%s\t%s",
				ctype,
				imageName,
				cStatus,
				info.CreatedAt.Round(time.Second).Local().String(),
			); err != nil {
				return err
			}

			if _, err := fmt.Fprint(w, "\n"); err != nil {
				return err
			}
		}
		return w.Flush()
	},
}
View Source
var ListTapCommand = &cli.Command{
	Name:  "taps",
	Usage: "list all taps",
	Flags: commands.NetworkAgentFlags(),
	Action: func(clictx *cli.Context) error {
		ctx, cancel := gocontext.WithTimeout(gocontext.Background(), clictx.Duration("timeout"))
		defer cancel()

		endpoint, err := commands.ResolveNetworkAgentEndpoint(clictx)
		if err != nil {
			return fmt.Errorf("resolve network-agent endpoint: %w", err)
		}
		naClient, err := networkagentclient.NewClient(endpoint)
		if err != nil {
			return fmt.Errorf("create network-agent client for %q: %w", endpoint, err)
		}
		listResp, err := naClient.ListNetworks(ctx, &networkagentclient.ListNetworksRequest{})
		if err != nil {
			return fmt.Errorf("list networks from network-agent: %w", err)
		}

		managedByIfindex := make(map[int]networkagentclient.NetworkState, len(listResp.Networks))
		managedByTapName := make(map[string]networkagentclient.NetworkState, len(listResp.Networks))
		for _, network := range listResp.Networks {
			if network.TapIfIndex != 0 {
				managedByIfindex[int(network.TapIfIndex)] = network
			}
			if network.TapName != "" {
				managedByTapName[network.TapName] = network
			}
		}

		links, err := netlink.LinkList()
		if err != nil {
			return fmt.Errorf("list netlink: %w", err)
		}

		w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
		fmt.Fprintln(w, "IP\tUSED\tINCUBEVS\tIFINDEX\tPORTMAPPING")
		var displayed []displayTap
		for _, link := range links {
			tap, ok := link.(*netlink.Tuntap)
			if !ok {
				continue
			}
			if tap.Mode != netlink.TUNTAP_MODE_TAP {
				continue
			}
			if !strings.HasPrefix(tap.Name, tapDeviceNamePrefix) {
				continue
			}

			s := tap.Name[len(tapDeviceNamePrefix):]
			ip := net.ParseIP(s)
			if ip == nil {
				fmt.Fprintf(os.Stderr, "invalid ip %q, skip", tap.Name)
				continue
			}

			managed, incubevs := managedByIfindex[tap.Index]
			if !incubevs {
				managed, incubevs = managedByTapName[tap.Name]
			}
			pm := append([]networkagentclient.PortMapping(nil), managed.PortMappings...)
			sort.Slice(pm, func(i, j int) bool {
				if pm[i].ContainerPort == pm[j].ContainerPort {
					return pm[i].HostPort < pm[j].HostPort
				}
				return pm[i].ContainerPort < pm[j].ContainerPort
			})

			displayed = append(displayed, displayTap{
				Name:        s,
				IfIndex:     tap.Index,
				Used:        link.Attrs().RawFlags&unix.IFF_LOWER_UP > 0,
				InCubeVS:    incubevs,
				PortMapping: pm,
			})
		}

		sort.Slice(displayed, func(i, j int) bool {
			return displayed[i].Name < displayed[j].Name
		})

		for _, tap := range displayed {
			fmt.Fprintf(w, "%s\t%t\t%t\t%d\t%v\n",
				tap.Name, tap.Used, tap.InCubeVS, tap.IfIndex, displayPortMapping(tap.PortMapping))
		}

		return w.Flush()
	},
}

Functions

func ContainerStatus

func ContainerStatus(ctx gocontext.Context, c containerd.Container) string

func TimeSinceInHuman

func TimeSinceInHuman(since time.Time) string

Types

type StdinCloser

type StdinCloser struct {
	// contains filtered or unexported fields
}

func (*StdinCloser) Read

func (s *StdinCloser) Read(p []byte) (int, error)

func (*StdinCloser) SetCloser

func (s *StdinCloser) SetCloser(closer func())

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL