github.com/containerd/nerdctl@v1.7.7/pkg/formatter/formatter.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package formatter 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "fmt" 24 "strconv" 25 "strings" 26 "time" 27 28 "golang.org/x/text/cases" 29 "golang.org/x/text/language" 30 31 "github.com/containerd/containerd" 32 "github.com/containerd/containerd/oci" 33 "github.com/containerd/containerd/runtime/restart" 34 "github.com/containerd/errdefs" 35 "github.com/containerd/log" 36 "github.com/containerd/nerdctl/pkg/portutil" 37 "github.com/docker/go-units" 38 ) 39 40 func ContainerStatus(ctx context.Context, c containerd.Container) string { 41 // Just in case, there is something wrong in server. 42 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 43 defer cancel() 44 titleCaser := cases.Title(language.English) 45 46 task, err := c.Task(ctx, nil) 47 if err != nil { 48 // NOTE: NotFound doesn't mean that container hasn't started. 49 // In docker/CRI-containerd plugin, the task will be deleted 50 // when it exits. So, the status will be "created" for this 51 // case. 52 if errdefs.IsNotFound(err) { 53 return titleCaser.String(string(containerd.Created)) 54 } 55 return titleCaser.String(string(containerd.Unknown)) 56 } 57 58 status, err := task.Status(ctx) 59 if err != nil { 60 return titleCaser.String(string(containerd.Unknown)) 61 } 62 labels, err := c.Labels(ctx) 63 if err != nil { 64 return titleCaser.String(string(containerd.Unknown)) 65 } 66 67 switch s := status.Status; s { 68 case containerd.Stopped: 69 if labels[restart.StatusLabel] == string(containerd.Running) && restart.Reconcile(status, labels) { 70 return fmt.Sprintf("Restarting (%v) %s", status.ExitStatus, TimeSinceInHuman(status.ExitTime)) 71 } 72 return fmt.Sprintf("Exited (%v) %s", status.ExitStatus, TimeSinceInHuman(status.ExitTime)) 73 case containerd.Running: 74 return "Up" // TODO: print "status.UpTime" (inexistent yet) 75 default: 76 return titleCaser.String(string(s)) 77 } 78 } 79 80 func InspectContainerCommand(spec *oci.Spec, trunc, quote bool) string { 81 if spec == nil || spec.Process == nil { 82 return "" 83 } 84 85 command := spec.Process.CommandLine + strings.Join(spec.Process.Args, " ") 86 if trunc { 87 command = Ellipsis(command, 20) 88 } 89 if quote { 90 command = strconv.Quote(command) 91 } 92 return command 93 } 94 95 func InspectContainerCommandTrunc(spec *oci.Spec) string { 96 return InspectContainerCommand(spec, true, true) 97 } 98 99 func Ellipsis(str string, maxDisplayWidth int) string { 100 if maxDisplayWidth <= 0 { 101 return "" 102 } 103 104 lenStr := len(str) 105 if maxDisplayWidth == 1 { 106 if lenStr <= maxDisplayWidth { 107 return str 108 } 109 return string(str[0]) 110 } 111 112 if lenStr <= maxDisplayWidth { 113 return str 114 } 115 return str[:maxDisplayWidth-1] + "…" 116 } 117 118 func FormatPorts(labelMap map[string]string) string { 119 ports, err := portutil.ParsePortsLabel(labelMap) 120 if err != nil { 121 log.L.Error(err.Error()) 122 } 123 if len(ports) == 0 { 124 return "" 125 } 126 strs := make([]string, len(ports)) 127 for i, p := range ports { 128 strs[i] = fmt.Sprintf("%s:%d->%d/%s", p.HostIP, p.HostPort, p.ContainerPort, p.Protocol) 129 } 130 return strings.Join(strs, ", ") 131 } 132 133 func TimeSinceInHuman(since time.Time) string { 134 return fmt.Sprintf("%s ago", units.HumanDuration(time.Since(since))) 135 } 136 137 func FormatLabels(labelMap map[string]string) string { 138 strs := make([]string, len(labelMap)) 139 idx := 0 140 for i := range labelMap { 141 strs[idx] = fmt.Sprintf("%s=%s", i, labelMap[i]) 142 idx++ 143 } 144 return strings.Join(strs, ",") 145 } 146 147 // ToJSON return a string with the JSON representation of the interface{} 148 // https://github.com/docker/compose/blob/v2/cmd/formatter/json.go#L31C4-L39 149 func ToJSON(i interface{}, prefix string, indentation string) (string, error) { 150 buffer := &bytes.Buffer{} 151 encoder := json.NewEncoder(buffer) 152 encoder.SetEscapeHTML(false) 153 encoder.SetIndent(prefix, indentation) 154 err := encoder.Encode(i) 155 return buffer.String(), err 156 }