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  }