From 5d3d7e0cada2cb067ee1399f2b3d58a8b1e6dcce Mon Sep 17 00:00:00 2001 From: Lantao Liu Date: Tue, 5 Dec 2017 17:52:55 +0000 Subject: [PATCH] Rewrite timestamps in container/sandbox status. Signed-off-by: Lantao Liu --- cmd/crictl/container.go | 26 ++++++++++++++++++- cmd/crictl/image.go | 6 ++++- cmd/crictl/info.go | 6 ++++- cmd/crictl/sandbox.go | 23 ++++++++++++++++- cmd/crictl/util.go | 55 +++++++++++++++++++++++++++++++++++------ 5 files changed, 105 insertions(+), 11 deletions(-) diff --git a/cmd/crictl/container.go b/cmd/crictl/container.go index 9778484a8a..6f607812aa 100644 --- a/cmd/crictl/container.go +++ b/cmd/crictl/container.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "encoding/json" "fmt" "log" "os" @@ -459,6 +460,24 @@ func RemoveContainer(client pb.RuntimeServiceClient, ID string) error { return nil } +// marshalContainerStatus converts container status into string and converts +// the timestamps into readable format. +func marshalContainerStatus(cs *pb.ContainerStatus) (string, error) { + statusStr, err := protobufObjectToJSON(cs) + if err != nil { + return "", err + } + jsonMap := make(map[string]interface{}) + err = json.Unmarshal([]byte(statusStr), &jsonMap) + if err != nil { + return "", err + } + jsonMap["createdAt"] = time.Unix(0, cs.CreatedAt).Format(time.RFC3339Nano) + jsonMap["startedAt"] = time.Unix(0, cs.StartedAt).Format(time.RFC3339Nano) + jsonMap["finishedAt"] = time.Unix(0, cs.FinishedAt).Format(time.RFC3339Nano) + return marshalMapInOrder(jsonMap, *cs) +} + // ContainerStatus sends a ContainerStatusRequest to the server, and parses // the returned ContainerStatusResponse. func ContainerStatus(client pb.RuntimeServiceClient, ID, output string, quiet bool) error { @@ -480,9 +499,14 @@ func ContainerStatus(client pb.RuntimeServiceClient, ID, output string, quiet bo return err } + status, err := marshalContainerStatus(r.Status) + if err != nil { + return err + } + switch output { case "json", "yaml": - return outputStatusInfo(r.Status, r.Info, output) + return outputStatusInfo(status, r.Info, output) case "table": // table output is after this switch block default: return fmt.Errorf("output option cannot be %s", output) diff --git a/cmd/crictl/image.go b/cmd/crictl/image.go index 2d154ef86b..6e3c606046 100644 --- a/cmd/crictl/image.go +++ b/cmd/crictl/image.go @@ -217,9 +217,13 @@ var imageStatusCommand = cli.Command{ output = "json" } + status, err := protobufObjectToJSON(r.Image) + if err != nil { + return err + } switch output { case "json", "yaml": - return outputStatusInfo(r.Image, r.Info, output) + return outputStatusInfo(status, r.Info, output) case "table": // table output is after this switch block default: return fmt.Errorf("output option cannot be %s", output) diff --git a/cmd/crictl/info.go b/cmd/crictl/info.go index 7db4ea6784..7c2371f70d 100644 --- a/cmd/crictl/info.go +++ b/cmd/crictl/info.go @@ -57,5 +57,9 @@ func Info(cliContext *cli.Context, client pb.RuntimeServiceClient) error { return err } - return outputStatusInfo(r.Status, r.Info, cliContext.String("output")) + status, err := protobufObjectToJSON(r.Status) + if err != nil { + return err + } + return outputStatusInfo(status, r.Info, cliContext.String("output")) } diff --git a/cmd/crictl/sandbox.go b/cmd/crictl/sandbox.go index aa0d36285f..2f78140420 100644 --- a/cmd/crictl/sandbox.go +++ b/cmd/crictl/sandbox.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "encoding/json" "fmt" "log" "os" @@ -272,6 +273,22 @@ func RemovePodSandbox(client pb.RuntimeServiceClient, ID string) error { return nil } +// marshalPodSandboxStatus converts pod sandbox status into string and converts +// the timestamps into readable format. +func marshalPodSandboxStatus(ps *pb.PodSandboxStatus) (string, error) { + statusStr, err := protobufObjectToJSON(ps) + if err != nil { + return "", err + } + jsonMap := make(map[string]interface{}) + err = json.Unmarshal([]byte(statusStr), &jsonMap) + if err != nil { + return "", err + } + jsonMap["createdAt"] = time.Unix(0, ps.CreatedAt).Format(time.RFC3339Nano) + return marshalMapInOrder(jsonMap, *ps) +} + // PodSandboxStatus sends a PodSandboxStatusRequest to the server, and parses // the returned PodSandboxStatusResponse. func PodSandboxStatus(client pb.RuntimeServiceClient, ID, output string, quiet bool) error { @@ -294,9 +311,13 @@ func PodSandboxStatus(client pb.RuntimeServiceClient, ID, output string, quiet b return err } + status, err := marshalPodSandboxStatus(r.Status) + if err != nil { + return err + } switch output { case "json", "yaml": - return outputStatusInfo(r.Status, r.Info, output) + return outputStatusInfo(status, r.Info, output) case "table": // table output is after this switch block default: return fmt.Errorf("output option cannot be %s", output) diff --git a/cmd/crictl/util.go b/cmd/crictl/util.go index 6150f03dc0..69e40c8fb6 100644 --- a/cmd/crictl/util.go +++ b/cmd/crictl/util.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "os" + "reflect" "sort" "strings" @@ -207,14 +208,17 @@ func outputProtobufObjAsYAML(obj proto.Message) error { return nil } -func outputStatusInfo(status proto.Message, info map[string]string, format string) error { - statusByte, err := protobufObjectToJSON(status) - if err != nil { - return err +func outputStatusInfo(status string, info map[string]string, format string) error { + // Sort all keys + var keys []string + for k := range info { + keys = append(keys, k) } - jsonInfo := "{" + "\"status\":" + string(statusByte) + "," - for k, v := range info { - jsonInfo += "\"" + k + "\"" + ":" + v + "," + sort.Strings(keys) + + jsonInfo := "{" + "\"status\":" + status + "," + for _, k := range keys { + jsonInfo += "\"" + k + "\"" + ":" + info[k] + "," } jsonInfo = jsonInfo[:len(jsonInfo)-1] jsonInfo += "}" @@ -249,3 +253,40 @@ func parseLabelStringSlice(ss []string) (map[string]string, error) { } return labels, nil } + +// marshalMapInOrder marshalls a map into json in the order of the original +// data structure. +func marshalMapInOrder(m map[string]interface{}, t interface{}) (string, error) { + s := "{" + v := reflect.ValueOf(t) + for i := 0; i < v.Type().NumField(); i++ { + field := jsonFieldFromTag(v.Type().Field(i).Tag) + if field == "" { + continue + } + value, err := json.Marshal(m[field]) + if err != nil { + return "", err + } + s += fmt.Sprintf("%q:%s,", field, value) + } + s = s[:len(s)-1] + s += "}" + var buf bytes.Buffer + if err := json.Indent(&buf, []byte(s), "", " "); err != nil { + return "", err + } + return buf.String(), nil +} + +// jsonFieldFromTag gets json field name from field tag. +func jsonFieldFromTag(tag reflect.StructTag) string { + field := strings.Split(tag.Get("json"), ",")[0] + for _, f := range strings.Split(tag.Get("protobuf"), ",") { + if !strings.HasPrefix(f, "json=") { + continue + } + field = strings.TrimPrefix(f, "json=") + } + return field +}