summaryrefslogtreecommitdiff
path: root/cmd/crioctl
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/crioctl')
-rw-r--r--cmd/crioctl/container.go653
-rw-r--r--cmd/crioctl/image.go173
-rw-r--r--cmd/crioctl/info.go31
-rw-r--r--cmd/crioctl/main.go113
-rw-r--r--cmd/crioctl/sandbox.go386
-rw-r--r--cmd/crioctl/system.go41
6 files changed, 1397 insertions, 0 deletions
diff --git a/cmd/crioctl/container.go b/cmd/crioctl/container.go
new file mode 100644
index 000000000..e420e6c99
--- /dev/null
+++ b/cmd/crioctl/container.go
@@ -0,0 +1,653 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/kubernetes-incubator/cri-o/client"
+ "github.com/urfave/cli"
+ "golang.org/x/net/context"
+ remocommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
+ restclient "k8s.io/client-go/rest"
+ "k8s.io/client-go/tools/remotecommand"
+ pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
+)
+
+var containerCommand = cli.Command{
+ Name: "container",
+ Aliases: []string{"ctr"},
+ Subcommands: []cli.Command{
+ createContainerCommand,
+ inspectContainerCommand,
+ startContainerCommand,
+ stopContainerCommand,
+ removeContainerCommand,
+ containerStatusCommand,
+ listContainersCommand,
+ execSyncCommand,
+ execCommand,
+ },
+}
+
+type createOptions struct {
+ // configPath is path to the config for container
+ configPath string
+ // name sets the container name
+ name string
+ // podID of the container
+ podID string
+ // labels for the container
+ labels map[string]string
+}
+
+var createContainerCommand = cli.Command{
+ Name: "create",
+ Usage: "create a container",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "pod",
+ Usage: "the id of the pod sandbox to which the container belongs",
+ },
+ cli.StringFlag{
+ Name: "config",
+ Value: "config.json",
+ Usage: "the path of a container config file",
+ },
+ cli.StringFlag{
+ Name: "name",
+ Value: "",
+ Usage: "the name of the container",
+ },
+ cli.StringSliceFlag{
+ Name: "label",
+ Usage: "add key=value labels to the container",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ if !context.IsSet("pod") {
+ return fmt.Errorf("Please specify the id of the pod sandbox to which the container belongs via the --pod option")
+ }
+
+ opts := createOptions{
+ configPath: context.String("config"),
+ name: context.String("name"),
+ podID: context.String("pod"),
+ labels: make(map[string]string),
+ }
+
+ for _, l := range context.StringSlice("label") {
+ pair := strings.Split(l, "=")
+ if len(pair) != 2 {
+ return fmt.Errorf("incorrectly specified label: %v", l)
+ }
+ opts.labels[pair[0]] = pair[1]
+ }
+
+ // Test RuntimeServiceClient.CreateContainer
+ err = CreateContainer(client, opts)
+ if err != nil {
+ return fmt.Errorf("Creating container failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var startContainerCommand = cli.Command{
+ Name: "start",
+ Usage: "start a container",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the container",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = StartContainer(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("Starting the container failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var stopContainerCommand = cli.Command{
+ Name: "stop",
+ Usage: "stop a container",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the container",
+ },
+ cli.Int64Flag{
+ Name: "timeout",
+ Value: 10,
+ Usage: "seconds to wait to kill the container after a graceful stop is requested",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = StopContainer(client, context.String("id"), context.Int64("timeout"))
+ if err != nil {
+ return fmt.Errorf("Stopping the container failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var removeContainerCommand = cli.Command{
+ Name: "remove",
+ Usage: "remove a container",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the container",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = RemoveContainer(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("Removing the container failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var containerStatusCommand = cli.Command{
+ Name: "status",
+ Usage: "get the status of a container",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the container",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = ContainerStatus(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("Getting the status of the container failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var execSyncCommand = cli.Command{
+ Name: "execsync",
+ Usage: "exec a command synchronously in a container",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the container",
+ },
+ cli.Int64Flag{
+ Name: "timeout",
+ Value: 0,
+ Usage: "timeout for the command",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = ExecSync(client, context.String("id"), context.Args(), context.Int64("timeout"))
+ if err != nil {
+ return fmt.Errorf("execing command in container failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var execCommand = cli.Command{
+ Name: "exec",
+ Usage: "prepare a streaming endpoint to execute a command in the container",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the container",
+ },
+ cli.BoolFlag{
+ Name: "tty",
+ Usage: "whether to use tty",
+ },
+ cli.BoolFlag{
+ Name: "stdin",
+ Usage: "whether to stream to stdin",
+ },
+ cli.BoolFlag{
+ Name: "url",
+ Usage: "do not exec command, just prepare streaming endpoint",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = Exec(client, context.String("id"), context.Bool("tty"), context.Bool("stdin"), context.Bool("url"), context.Args())
+ if err != nil {
+ return fmt.Errorf("execing command in container failed: %v", err)
+ }
+ return nil
+ },
+}
+
+type listOptions struct {
+ // id of the container
+ id string
+ // podID of the container
+ podID string
+ // state of the container
+ state string
+ // quiet is for listing just container IDs
+ quiet bool
+ // labels are selectors for the container
+ labels map[string]string
+}
+
+var listContainersCommand = cli.Command{
+ Name: "list",
+ Usage: "list containers",
+ Flags: []cli.Flag{
+ cli.BoolFlag{
+ Name: "quiet",
+ Usage: "list only container IDs",
+ },
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "filter by container id",
+ },
+ cli.StringFlag{
+ Name: "pod",
+ Value: "",
+ Usage: "filter by container pod id",
+ },
+ cli.StringFlag{
+ Name: "state",
+ Value: "",
+ Usage: "filter by container state",
+ },
+ cli.StringSliceFlag{
+ Name: "label",
+ Usage: "filter by key=value label",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+ opts := listOptions{
+ id: context.String("id"),
+ podID: context.String("pod"),
+ state: context.String("state"),
+ quiet: context.Bool("quiet"),
+ labels: make(map[string]string),
+ }
+
+ for _, l := range context.StringSlice("label") {
+ pair := strings.Split(l, "=")
+ if len(pair) != 2 {
+ return fmt.Errorf("incorrectly specified label: %v", l)
+ }
+ opts.labels[pair[0]] = pair[1]
+ }
+
+ err = ListContainers(client, opts)
+ if err != nil {
+ return fmt.Errorf("listing containers failed: %v", err)
+ }
+ return nil
+ },
+}
+
+// CreateContainer sends a CreateContainerRequest to the server, and parses
+// the returned CreateContainerResponse.
+func CreateContainer(client pb.RuntimeServiceClient, opts createOptions) error {
+ config, err := loadContainerConfig(opts.configPath)
+ if err != nil {
+ return err
+ }
+
+ // Override the name by the one specified through CLI
+ if opts.name != "" {
+ config.Metadata.Name = opts.name
+ }
+
+ for k, v := range opts.labels {
+ config.Labels[k] = v
+ }
+
+ r, err := client.CreateContainer(context.Background(), &pb.CreateContainerRequest{
+ PodSandboxId: opts.podID,
+ Config: config,
+ // TODO(runcom): this is missing PodSandboxConfig!!!
+ // we should/could find a way to retrieve it from the fs and set it here
+ })
+ if err != nil {
+ return err
+ }
+ fmt.Println(r.ContainerId)
+ return nil
+}
+
+// StartContainer sends a StartContainerRequest to the server, and parses
+// the returned StartContainerResponse.
+func StartContainer(client pb.RuntimeServiceClient, ID string) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ _, err := client.StartContainer(context.Background(), &pb.StartContainerRequest{
+ ContainerId: ID,
+ })
+ if err != nil {
+ return err
+ }
+ fmt.Println(ID)
+ return nil
+}
+
+// StopContainer sends a StopContainerRequest to the server, and parses
+// the returned StopContainerResponse.
+func StopContainer(client pb.RuntimeServiceClient, ID string, timeout int64) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ _, err := client.StopContainer(context.Background(), &pb.StopContainerRequest{
+ ContainerId: ID,
+ Timeout: timeout,
+ })
+ if err != nil {
+ return err
+ }
+ fmt.Println(ID)
+ return nil
+}
+
+// RemoveContainer sends a RemoveContainerRequest to the server, and parses
+// the returned RemoveContainerResponse.
+func RemoveContainer(client pb.RuntimeServiceClient, ID string) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ _, err := client.RemoveContainer(context.Background(), &pb.RemoveContainerRequest{
+ ContainerId: ID,
+ })
+ if err != nil {
+ return err
+ }
+ fmt.Println(ID)
+ return nil
+}
+
+// ContainerStatus sends a ContainerStatusRequest to the server, and parses
+// the returned ContainerStatusResponse.
+func ContainerStatus(client pb.RuntimeServiceClient, ID string) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ r, err := client.ContainerStatus(context.Background(), &pb.ContainerStatusRequest{
+ ContainerId: ID})
+ if err != nil {
+ return err
+ }
+ fmt.Printf("ID: %s\n", r.Status.Id)
+ if r.Status.Metadata != nil {
+ if r.Status.Metadata.Name != "" {
+ fmt.Printf("Name: %s\n", r.Status.Metadata.Name)
+ }
+ fmt.Printf("Attempt: %v\n", r.Status.Metadata.Attempt)
+ }
+ // TODO(mzylowski): print it prettier
+ fmt.Printf("Status: %s\n", r.Status.State)
+ ctm := time.Unix(0, r.Status.CreatedAt)
+ fmt.Printf("Created: %v\n", ctm)
+ stm := time.Unix(0, r.Status.StartedAt)
+ fmt.Printf("Started: %v\n", stm)
+ ftm := time.Unix(0, r.Status.FinishedAt)
+ fmt.Printf("Finished: %v\n", ftm)
+ fmt.Printf("Exit Code: %v\n", r.Status.ExitCode)
+ fmt.Printf("Reason: %v\n", r.Status.Reason)
+ if r.Status.Image != nil {
+ fmt.Printf("Image: %v\n", r.Status.Image.Image)
+ }
+ fmt.Printf("ImageRef: %v\n", r.Status.ImageRef)
+
+ return nil
+}
+
+// ExecSync sends an ExecSyncRequest to the server, and parses
+// the returned ExecSyncResponse.
+func ExecSync(client pb.RuntimeServiceClient, ID string, cmd []string, timeout int64) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ r, err := client.ExecSync(context.Background(), &pb.ExecSyncRequest{
+ ContainerId: ID,
+ Cmd: cmd,
+ Timeout: timeout,
+ })
+ if err != nil {
+ return err
+ }
+ fmt.Println("Stdout:")
+ fmt.Println(string(r.Stdout))
+ fmt.Println("Stderr:")
+ fmt.Println(string(r.Stderr))
+ fmt.Printf("Exit code: %v\n", r.ExitCode)
+
+ return nil
+}
+
+// Exec sends an ExecRequest to the server, and parses
+// the returned ExecResponse.
+func Exec(client pb.RuntimeServiceClient, ID string, tty bool, stdin bool, urlOnly bool, cmd []string) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ r, err := client.Exec(context.Background(), &pb.ExecRequest{
+ ContainerId: ID,
+ Cmd: cmd,
+ Tty: tty,
+ Stdin: stdin,
+ })
+ if err != nil {
+ return err
+ }
+
+ if urlOnly {
+ fmt.Println("URL:")
+ fmt.Println(r.Url)
+ return nil
+ }
+
+ execURL, err := url.Parse(r.Url)
+ if err != nil {
+ return err
+ }
+
+ streamExec, err := remotecommand.NewExecutor(&restclient.Config{}, "GET", execURL)
+ if err != nil {
+ return err
+ }
+
+ options := remotecommand.StreamOptions{
+ SupportedProtocols: remocommandconsts.SupportedStreamingProtocols,
+ Stdout: os.Stdout,
+ Stderr: os.Stderr,
+ Tty: tty,
+ }
+
+ if stdin {
+ options.Stdin = os.Stdin
+ }
+
+ return streamExec.Stream(options)
+}
+
+// ListContainers sends a ListContainerRequest to the server, and parses
+// the returned ListContainerResponse.
+func ListContainers(client pb.RuntimeServiceClient, opts listOptions) error {
+ filter := &pb.ContainerFilter{}
+ if opts.id != "" {
+ filter.Id = opts.id
+ }
+ if opts.podID != "" {
+ filter.PodSandboxId = opts.podID
+ }
+ if opts.state != "" {
+ st := &pb.ContainerStateValue{}
+ st.State = pb.ContainerState_CONTAINER_UNKNOWN
+ switch opts.state {
+ case "created":
+ st.State = pb.ContainerState_CONTAINER_CREATED
+ filter.State = st
+ case "running":
+ st.State = pb.ContainerState_CONTAINER_RUNNING
+ filter.State = st
+ case "stopped":
+ st.State = pb.ContainerState_CONTAINER_EXITED
+ filter.State = st
+ default:
+ log.Fatalf("--state should be one of created, running or stopped")
+ }
+ }
+ if opts.labels != nil {
+ filter.LabelSelector = opts.labels
+ }
+ r, err := client.ListContainers(context.Background(), &pb.ListContainersRequest{
+ Filter: filter,
+ })
+ if err != nil {
+ return err
+ }
+ for _, c := range r.GetContainers() {
+ if opts.quiet {
+ fmt.Println(c.Id)
+ continue
+ }
+ fmt.Printf("ID: %s\n", c.Id)
+ fmt.Printf("Pod: %s\n", c.PodSandboxId)
+ if c.Metadata != nil {
+ if c.Metadata.Name != "" {
+ fmt.Printf("Name: %s\n", c.Metadata.Name)
+ }
+ fmt.Printf("Attempt: %v\n", c.Metadata.Attempt)
+ }
+ fmt.Printf("Status: %s\n", c.State)
+ if c.Image != nil {
+ fmt.Printf("Image: %s\n", c.Image.Image)
+ }
+ ctm := time.Unix(0, c.CreatedAt)
+ fmt.Printf("Created: %v\n", ctm)
+ if c.Labels != nil {
+ fmt.Println("Labels:")
+ for _, k := range getSortedKeys(c.Labels) {
+ fmt.Printf("\t%s -> %s\n", k, c.Labels[k])
+ }
+ }
+ if c.Annotations != nil {
+ fmt.Println("Annotations:")
+ for _, k := range getSortedKeys(c.Annotations) {
+ fmt.Printf("\t%s -> %s\n", k, c.Annotations[k])
+ }
+ }
+ fmt.Println()
+ }
+ return nil
+}
+
+var inspectContainerCommand = cli.Command{
+ Name: "inspect",
+ Usage: "get container info from crio daemon",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the container",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ ID := context.String("id")
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ c, err := client.New(context.GlobalString("connect"))
+ if err != nil {
+ return err
+ }
+
+ cInfo, err := c.ContainerInfo(ID)
+ if err != nil {
+ return err
+ }
+
+ jsonBytes, err := json.MarshalIndent(cInfo, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(jsonBytes))
+ return nil
+ },
+}
diff --git a/cmd/crioctl/image.go b/cmd/crioctl/image.go
new file mode 100644
index 000000000..426c67e9d
--- /dev/null
+++ b/cmd/crioctl/image.go
@@ -0,0 +1,173 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/urfave/cli"
+ "golang.org/x/net/context"
+ pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
+)
+
+var imageCommand = cli.Command{
+ Name: "image",
+ Subcommands: []cli.Command{
+ pullImageCommand,
+ listImageCommand,
+ imageStatusCommand,
+ removeImageCommand,
+ },
+}
+
+var pullImageCommand = cli.Command{
+ Name: "pull",
+ Usage: "pull an image",
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewImageServiceClient(conn)
+
+ _, err = PullImage(client, context.Args().Get(0))
+ if err != nil {
+ return fmt.Errorf("pulling image failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var listImageCommand = cli.Command{
+ Name: "list",
+ Usage: "list images",
+ Flags: []cli.Flag{
+ cli.BoolFlag{
+ Name: "quiet",
+ Usage: "list only image IDs",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewImageServiceClient(conn)
+
+ r, err := ListImages(client, context.Args().Get(0))
+ if err != nil {
+ return fmt.Errorf("listing images failed: %v", err)
+ }
+ quiet := context.Bool("quiet")
+ for _, image := range r.Images {
+ if quiet {
+ fmt.Printf("%s\n", image.Id)
+ continue
+ }
+ fmt.Printf("ID: %s\n", image.Id)
+ for _, tag := range image.RepoTags {
+ fmt.Printf("Tag: %s\n", tag)
+ }
+ for _, digest := range image.RepoDigests {
+ fmt.Printf("Digest: %s\n", digest)
+ }
+ if image.Size_ != 0 {
+ fmt.Printf("Size: %d\n", image.Size_)
+ }
+ }
+ return nil
+ },
+}
+
+var imageStatusCommand = cli.Command{
+ Name: "status",
+ Usage: "return the status of an image",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Usage: "id of the image",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewImageServiceClient(conn)
+
+ r, err := ImageStatus(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("image status request failed: %v", err)
+ }
+ image := r.Image
+ if image == nil {
+ return fmt.Errorf("no such image present")
+ }
+ fmt.Printf("ID: %s\n", image.Id)
+ for _, tag := range image.RepoTags {
+ fmt.Printf("Tag: %s\n", tag)
+ }
+ for _, digest := range image.RepoDigests {
+ fmt.Printf("Digest: %s\n", digest)
+ }
+ fmt.Printf("Size: %d\n", image.Size_)
+ return nil
+ },
+}
+var removeImageCommand = cli.Command{
+ Name: "remove",
+ Usage: "remove an image",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the image",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewImageServiceClient(conn)
+
+ _, err = RemoveImage(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("removing the image failed: %v", err)
+ }
+ return nil
+ },
+}
+
+// PullImage sends a PullImageRequest to the server, and parses
+// the returned PullImageResponse.
+func PullImage(client pb.ImageServiceClient, image string) (*pb.PullImageResponse, error) {
+ return client.PullImage(context.Background(), &pb.PullImageRequest{Image: &pb.ImageSpec{Image: image}})
+}
+
+// ListImages sends a ListImagesRequest to the server, and parses
+// the returned ListImagesResponse.
+func ListImages(client pb.ImageServiceClient, image string) (*pb.ListImagesResponse, error) {
+ return client.ListImages(context.Background(), &pb.ListImagesRequest{Filter: &pb.ImageFilter{Image: &pb.ImageSpec{Image: image}}})
+}
+
+// ImageStatus sends an ImageStatusRequest to the server, and parses
+// the returned ImageStatusResponse.
+func ImageStatus(client pb.ImageServiceClient, image string) (*pb.ImageStatusResponse, error) {
+ return client.ImageStatus(context.Background(), &pb.ImageStatusRequest{Image: &pb.ImageSpec{Image: image}})
+}
+
+// RemoveImage sends a RemoveImageRequest to the server, and parses
+// the returned RemoveImageResponse.
+func RemoveImage(client pb.ImageServiceClient, image string) (*pb.RemoveImageResponse, error) {
+ if image == "" {
+ return nil, fmt.Errorf("ID cannot be empty")
+ }
+ return client.RemoveImage(context.Background(), &pb.RemoveImageRequest{Image: &pb.ImageSpec{Image: image}})
+}
diff --git a/cmd/crioctl/info.go b/cmd/crioctl/info.go
new file mode 100644
index 000000000..1f06f594a
--- /dev/null
+++ b/cmd/crioctl/info.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/kubernetes-incubator/cri-o/client"
+ "github.com/urfave/cli"
+)
+
+var infoCommand = cli.Command{
+ Name: "info",
+ Usage: "get crio daemon info",
+ Action: func(context *cli.Context) error {
+ c, err := client.New(context.GlobalString("connect"))
+ if err != nil {
+ return err
+ }
+ di, err := c.DaemonInfo()
+ if err != nil {
+ return err
+ }
+
+ jsonBytes, err := json.MarshalIndent(di, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(jsonBytes))
+ return nil
+ },
+}
diff --git a/cmd/crioctl/main.go b/cmd/crioctl/main.go
new file mode 100644
index 000000000..3d77867fe
--- /dev/null
+++ b/cmd/crioctl/main.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+ "google.golang.org/grpc"
+ pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
+)
+
+// This is populated by the Makefile from the VERSION file
+// in the repository
+var version = ""
+
+// gitCommit is the commit that the binary is being built from.
+// It will be populated by the Makefile.
+var gitCommit = ""
+
+func getClientConnection(context *cli.Context) (*grpc.ClientConn, error) {
+ conn, err := grpc.Dial(context.GlobalString("connect"), grpc.WithInsecure(), grpc.WithTimeout(context.GlobalDuration("timeout")),
+ grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
+ return net.DialTimeout("unix", addr, timeout)
+ }))
+ if err != nil {
+ return nil, fmt.Errorf("failed to connect: %v", err)
+ }
+ return conn, nil
+}
+
+func openFile(path string) (*os.File, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, fmt.Errorf("config at %s not found", path)
+ }
+ return nil, err
+ }
+ return f, nil
+}
+
+func loadPodSandboxConfig(path string) (*pb.PodSandboxConfig, error) {
+ f, err := openFile(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ var config pb.PodSandboxConfig
+ if err := json.NewDecoder(f).Decode(&config); err != nil {
+ return nil, err
+ }
+ return &config, nil
+}
+
+func loadContainerConfig(path string) (*pb.ContainerConfig, error) {
+ f, err := openFile(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ var config pb.ContainerConfig
+ if err := json.NewDecoder(f).Decode(&config); err != nil {
+ return nil, err
+ }
+ return &config, nil
+}
+
+func main() {
+ app := cli.NewApp()
+ var v []string
+ if version != "" {
+ v = append(v, version)
+ }
+ if gitCommit != "" {
+ v = append(v, fmt.Sprintf("commit: %s", gitCommit))
+ }
+
+ app.Name = "crioctl"
+ app.Usage = "client for crio"
+ app.Version = strings.Join(v, "\n")
+
+ app.Commands = []cli.Command{
+ podSandboxCommand,
+ containerCommand,
+ runtimeVersionCommand,
+ imageCommand,
+ infoCommand,
+ }
+
+ app.Flags = []cli.Flag{
+ cli.StringFlag{
+ Name: "connect",
+ Value: "/var/run/crio.sock",
+ Usage: "Socket to connect to",
+ },
+ cli.DurationFlag{
+ Name: "timeout",
+ Value: 10 * time.Second,
+ Usage: "Timeout of connecting to server",
+ },
+ }
+
+ if err := app.Run(os.Args); err != nil {
+ logrus.Fatal(err)
+ }
+}
diff --git a/cmd/crioctl/sandbox.go b/cmd/crioctl/sandbox.go
new file mode 100644
index 000000000..e44183be3
--- /dev/null
+++ b/cmd/crioctl/sandbox.go
@@ -0,0 +1,386 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/urfave/cli"
+ "golang.org/x/net/context"
+ pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
+)
+
+var podSandboxCommand = cli.Command{
+ Name: "pod",
+ Subcommands: []cli.Command{
+ runPodSandboxCommand,
+ stopPodSandboxCommand,
+ removePodSandboxCommand,
+ podSandboxStatusCommand,
+ listPodSandboxCommand,
+ },
+}
+
+var runPodSandboxCommand = cli.Command{
+ Name: "run",
+ Usage: "run a pod",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "config",
+ Value: "",
+ Usage: "the path of a pod sandbox config file",
+ },
+ cli.StringFlag{
+ Name: "name",
+ Value: "",
+ Usage: "the name of the pod sandbox",
+ },
+ cli.StringSliceFlag{
+ Name: "label",
+ Usage: "add key=value labels to the container",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ opts := createOptions{
+ configPath: context.String("config"),
+ name: context.String("name"),
+ labels: make(map[string]string),
+ }
+
+ for _, l := range context.StringSlice("label") {
+ pair := strings.Split(l, "=")
+ if len(pair) != 2 {
+ return fmt.Errorf("incorrectly specified label: %v", l)
+ }
+ opts.labels[pair[0]] = pair[1]
+ }
+
+ // Test RuntimeServiceClient.RunPodSandbox
+ err = RunPodSandbox(client, opts)
+ if err != nil {
+ return fmt.Errorf("Creating the pod sandbox failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var stopPodSandboxCommand = cli.Command{
+ Name: "stop",
+ Usage: "stop a pod sandbox",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the pod sandbox",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = StopPodSandbox(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("stopping the pod sandbox failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var removePodSandboxCommand = cli.Command{
+ Name: "remove",
+ Usage: "remove a pod sandbox",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the pod sandbox",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = RemovePodSandbox(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("removing the pod sandbox failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var podSandboxStatusCommand = cli.Command{
+ Name: "status",
+ Usage: "return the status of a pod",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "id of the pod",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ err = PodSandboxStatus(client, context.String("id"))
+ if err != nil {
+ return fmt.Errorf("getting the pod sandbox status failed: %v", err)
+ }
+ return nil
+ },
+}
+
+var listPodSandboxCommand = cli.Command{
+ Name: "list",
+ Usage: "list pod sandboxes",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "id",
+ Value: "",
+ Usage: "filter by pod sandbox id",
+ },
+ cli.StringFlag{
+ Name: "state",
+ Value: "",
+ Usage: "filter by pod sandbox state",
+ },
+ cli.StringSliceFlag{
+ Name: "label",
+ Usage: "filter by key=value label",
+ },
+ cli.BoolFlag{
+ Name: "quiet",
+ Usage: "list only pod IDs",
+ },
+ },
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ opts := listOptions{
+ id: context.String("id"),
+ state: context.String("state"),
+ quiet: context.Bool("quiet"),
+ labels: make(map[string]string),
+ }
+
+ for _, l := range context.StringSlice("label") {
+ pair := strings.Split(l, "=")
+ if len(pair) != 2 {
+ return fmt.Errorf("incorrectly specified label: %v", l)
+ }
+ opts.labels[pair[0]] = pair[1]
+ }
+
+ err = ListPodSandboxes(client, opts)
+ if err != nil {
+ return fmt.Errorf("listing pod sandboxes failed: %v", err)
+ }
+ return nil
+ },
+}
+
+// RunPodSandbox sends a RunPodSandboxRequest to the server, and parses
+// the returned RunPodSandboxResponse.
+func RunPodSandbox(client pb.RuntimeServiceClient, opts createOptions) error {
+ config, err := loadPodSandboxConfig(opts.configPath)
+ if err != nil {
+ return err
+ }
+
+ // Override the name by the one specified through CLI
+ if opts.name != "" {
+ config.Metadata.Name = opts.name
+ }
+
+ for k, v := range opts.labels {
+ config.Labels[k] = v
+ }
+
+ r, err := client.RunPodSandbox(context.Background(), &pb.RunPodSandboxRequest{Config: config})
+ if err != nil {
+ return err
+ }
+ fmt.Println(r.PodSandboxId)
+ return nil
+}
+
+// StopPodSandbox sends a StopPodSandboxRequest to the server, and parses
+// the returned StopPodSandboxResponse.
+func StopPodSandbox(client pb.RuntimeServiceClient, ID string) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ _, err := client.StopPodSandbox(context.Background(), &pb.StopPodSandboxRequest{PodSandboxId: ID})
+ if err != nil {
+ return err
+ }
+ fmt.Println(ID)
+ return nil
+}
+
+// RemovePodSandbox sends a RemovePodSandboxRequest to the server, and parses
+// the returned RemovePodSandboxResponse.
+func RemovePodSandbox(client pb.RuntimeServiceClient, ID string) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ _, err := client.RemovePodSandbox(context.Background(), &pb.RemovePodSandboxRequest{PodSandboxId: ID})
+ if err != nil {
+ return err
+ }
+ fmt.Println(ID)
+ return nil
+}
+
+// PodSandboxStatus sends a PodSandboxStatusRequest to the server, and parses
+// the returned PodSandboxStatusResponse.
+func PodSandboxStatus(client pb.RuntimeServiceClient, ID string) error {
+ if ID == "" {
+ return fmt.Errorf("ID cannot be empty")
+ }
+ r, err := client.PodSandboxStatus(context.Background(), &pb.PodSandboxStatusRequest{PodSandboxId: ID})
+ if err != nil {
+ return err
+ }
+ fmt.Printf("ID: %s\n", r.Status.Id)
+ if r.Status.Metadata != nil {
+ if r.Status.Metadata.Name != "" {
+ fmt.Printf("Name: %s\n", r.Status.Metadata.Name)
+ }
+ if r.Status.Metadata.Uid != "" {
+ fmt.Printf("UID: %s\n", r.Status.Metadata.Uid)
+ }
+ if r.Status.Metadata.Namespace != "" {
+ fmt.Printf("Namespace: %s\n", r.Status.Metadata.Namespace)
+ }
+ fmt.Printf("Attempt: %v\n", r.Status.Metadata.Attempt)
+ }
+ fmt.Printf("Status: %s\n", r.Status.State)
+ ctm := time.Unix(0, r.Status.CreatedAt)
+ fmt.Printf("Created: %v\n", ctm)
+ if r.Status.Network != nil {
+ fmt.Printf("IP Address: %v\n", r.Status.Network.Ip)
+ }
+ if r.Status.Labels != nil {
+ fmt.Println("Labels:")
+ for _, k := range getSortedKeys(r.Status.Labels) {
+ fmt.Printf("\t%s -> %s\n", k, r.Status.Labels[k])
+ }
+ }
+ if r.Status.Annotations != nil {
+ fmt.Println("Annotations:")
+ for _, k := range getSortedKeys(r.Status.Annotations) {
+ fmt.Printf("\t%s -> %s\n", k, r.Status.Annotations[k])
+ }
+ }
+ return nil
+}
+
+// ListPodSandboxes sends a ListPodSandboxRequest to the server, and parses
+// the returned ListPodSandboxResponse.
+func ListPodSandboxes(client pb.RuntimeServiceClient, opts listOptions) error {
+ filter := &pb.PodSandboxFilter{}
+ if opts.id != "" {
+ filter.Id = opts.id
+ }
+ if opts.state != "" {
+ st := &pb.PodSandboxStateValue{}
+ st.State = pb.PodSandboxState_SANDBOX_NOTREADY
+ switch opts.state {
+ case "ready":
+ st.State = pb.PodSandboxState_SANDBOX_READY
+ filter.State = st
+ case "notready":
+ st.State = pb.PodSandboxState_SANDBOX_NOTREADY
+ filter.State = st
+ default:
+ log.Fatalf("--state should be ready or notready")
+ }
+ }
+ if opts.labels != nil {
+ filter.LabelSelector = opts.labels
+ }
+ r, err := client.ListPodSandbox(context.Background(), &pb.ListPodSandboxRequest{
+ Filter: filter,
+ })
+ if err != nil {
+ return err
+ }
+ for _, pod := range r.Items {
+ if opts.quiet {
+ fmt.Println(pod.Id)
+ continue
+ }
+ fmt.Printf("ID: %s\n", pod.Id)
+ if pod.Metadata != nil {
+ if pod.Metadata.Name != "" {
+ fmt.Printf("Name: %s\n", pod.Metadata.Name)
+ }
+ if pod.Metadata.Uid != "" {
+ fmt.Printf("UID: %s\n", pod.Metadata.Uid)
+ }
+ if pod.Metadata.Namespace != "" {
+ fmt.Printf("Namespace: %s\n", pod.Metadata.Namespace)
+ }
+ fmt.Printf("Attempt: %v\n", pod.Metadata.Attempt)
+ }
+ fmt.Printf("Status: %s\n", pod.State)
+ ctm := time.Unix(0, pod.CreatedAt)
+ fmt.Printf("Created: %v\n", ctm)
+ if pod.Labels != nil {
+ fmt.Println("Labels:")
+ for _, k := range getSortedKeys(pod.Labels) {
+ fmt.Printf("\t%s -> %s\n", k, pod.Labels[k])
+ }
+ }
+ if pod.Annotations != nil {
+ fmt.Println("Annotations:")
+ for _, k := range getSortedKeys(pod.Annotations) {
+ fmt.Printf("\t%s -> %s\n", k, pod.Annotations[k])
+ }
+ }
+ fmt.Println()
+ }
+ return nil
+}
+
+func getSortedKeys(m map[string]string) []string {
+ var keys []string
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ return keys
+}
diff --git a/cmd/crioctl/system.go b/cmd/crioctl/system.go
new file mode 100644
index 000000000..7e04161c2
--- /dev/null
+++ b/cmd/crioctl/system.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/urfave/cli"
+ "golang.org/x/net/context"
+ pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
+)
+
+var runtimeVersionCommand = cli.Command{
+ Name: "runtimeversion",
+ Usage: "get runtime version information",
+ Action: func(context *cli.Context) error {
+ // Set up a connection to the server.
+ conn, err := getClientConnection(context)
+ if err != nil {
+ return fmt.Errorf("failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := pb.NewRuntimeServiceClient(conn)
+
+ // Test RuntimeServiceClient.Version
+ version := "v1alpha1"
+ err = Version(client, version)
+ if err != nil {
+ return fmt.Errorf("Getting the runtime version failed: %v", err)
+ }
+ return nil
+ },
+}
+
+// Version sends a VersionRequest to the server, and parses the returned VersionResponse.
+func Version(client pb.RuntimeServiceClient, version string) error {
+ r, err := client.Version(context.Background(), &pb.VersionRequest{Version: version})
+ if err != nil {
+ return err
+ }
+ fmt.Printf("VersionResponse: Version: %s, RuntimeName: %s, RuntimeVersion: %s, RuntimeApiVersion: %s\n", r.Version, r.RuntimeName, r.RuntimeVersion, r.RuntimeApiVersion)
+ return nil
+}