From a031b83a09a8628435317a03f199cdc18b78262f Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 1 Nov 2017 11:24:59 -0400 Subject: Initial checkin from CRI-O repo Signed-off-by: Matthew Heon --- cmd/crioctl/container.go | 653 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/crioctl/image.go | 173 +++++++++++++ cmd/crioctl/info.go | 31 +++ cmd/crioctl/main.go | 113 ++++++++ cmd/crioctl/sandbox.go | 386 ++++++++++++++++++++++++++++ cmd/crioctl/system.go | 41 +++ 6 files changed, 1397 insertions(+) create mode 100644 cmd/crioctl/container.go create mode 100644 cmd/crioctl/image.go create mode 100644 cmd/crioctl/info.go create mode 100644 cmd/crioctl/main.go create mode 100644 cmd/crioctl/sandbox.go create mode 100644 cmd/crioctl/system.go (limited to 'cmd/crioctl') 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 +} -- cgit v1.2.3-54-g00ecf