github.com/docker/app@v0.9.1-beta3.0.20210611140623-a48f773ab002/cmd/cnab-run/status.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  	"text/tabwriter"
    11  
    12  	"github.com/docker/app/internal"
    13  	"github.com/docker/cli/cli/command"
    14  	"github.com/docker/cli/cli/command/stack"
    15  	"github.com/docker/cli/cli/command/stack/options"
    16  	"github.com/docker/cli/opts"
    17  	"github.com/docker/distribution/reference"
    18  	swarmtypes "github.com/docker/docker/api/types/swarm"
    19  	"github.com/docker/docker/pkg/stringid"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  var (
    24  	listColumns = []struct {
    25  		header string
    26  		value  func(s *swarmtypes.Service) string
    27  	}{
    28  		{"ID", func(s *swarmtypes.Service) string { return stringid.TruncateID(s.ID) }},
    29  		{"NAME", func(s *swarmtypes.Service) string { return s.Spec.Name }},
    30  		{"MODE", func(s *swarmtypes.Service) string {
    31  			if s.Spec.Mode.Replicated != nil {
    32  				return "replicated"
    33  			}
    34  			return "global"
    35  		}},
    36  		{"REPLICAS", func(s *swarmtypes.Service) string {
    37  			if s.Spec.Mode.Replicated != nil {
    38  				return fmt.Sprintf("%d/%d", s.ServiceStatus.RunningTasks, s.ServiceStatus.DesiredTasks)
    39  			}
    40  			return ""
    41  		}},
    42  		{"IMAGE", func(s *swarmtypes.Service) string {
    43  			ref, err := reference.ParseAnyReference(s.Spec.TaskTemplate.ContainerSpec.Image)
    44  			if err != nil {
    45  				return "N/A"
    46  			}
    47  			if namedRef, ok := ref.(reference.Named); ok {
    48  				return reference.FamiliarName(namedRef)
    49  			}
    50  			return reference.FamiliarString(ref)
    51  		}},
    52  		{"PORTS", func(s *swarmtypes.Service) string {
    53  			return ports(s.Endpoint.Ports)
    54  		}},
    55  	}
    56  )
    57  
    58  type portRange struct {
    59  	pStart   uint32
    60  	pEnd     uint32
    61  	tStart   uint32
    62  	tEnd     uint32
    63  	protocol swarmtypes.PortConfigProtocol
    64  }
    65  
    66  func (pr portRange) String() string {
    67  	var (
    68  		pub string
    69  		tgt string
    70  	)
    71  
    72  	if pr.pEnd > pr.pStart {
    73  		pub = fmt.Sprintf("%d-%d", pr.pStart, pr.pEnd)
    74  	} else {
    75  		pub = fmt.Sprintf("%d", pr.pStart)
    76  	}
    77  	if pr.tEnd > pr.tStart {
    78  		tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
    79  	} else {
    80  		tgt = fmt.Sprintf("%d", pr.tStart)
    81  	}
    82  	return fmt.Sprintf("*:%s->%s/%s", pub, tgt, pr.protocol)
    83  }
    84  
    85  // Ports formats port configuration. This function is copied et adapted from docker CLI
    86  // see https://github.com/docker/cli/blob/d6edc912ce/cli/command/service/formatter.go#L655
    87  func ports(servicePorts []swarmtypes.PortConfig) string {
    88  	if servicePorts == nil {
    89  		return ""
    90  	}
    91  
    92  	pr := portRange{}
    93  	ports := []string{}
    94  
    95  	sort.Slice(servicePorts, func(i, j int) bool {
    96  		if servicePorts[i].Protocol == servicePorts[j].Protocol {
    97  			return servicePorts[i].PublishedPort < servicePorts[j].PublishedPort
    98  		}
    99  		return servicePorts[i].Protocol < servicePorts[j].Protocol
   100  	})
   101  
   102  	for _, p := range servicePorts {
   103  		if p.PublishMode == swarmtypes.PortConfigPublishModeIngress {
   104  			prIsRange := pr.tEnd != pr.tStart
   105  			tOverlaps := p.TargetPort <= pr.tEnd
   106  
   107  			// Start a new port-range if:
   108  			// - the protocol is different from the current port-range
   109  			// - published or target port are not consecutive to the current port-range
   110  			// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
   111  			if p.Protocol != pr.protocol || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
   112  				// start a new port-range, and print the previous port-range (if any)
   113  				if pr.pStart > 0 {
   114  					ports = append(ports, pr.String())
   115  				}
   116  				pr = portRange{
   117  					pStart:   p.PublishedPort,
   118  					pEnd:     p.PublishedPort,
   119  					tStart:   p.TargetPort,
   120  					tEnd:     p.TargetPort,
   121  					protocol: p.Protocol,
   122  				}
   123  				continue
   124  			}
   125  			pr.pEnd = p.PublishedPort
   126  			pr.tEnd = p.TargetPort
   127  		}
   128  	}
   129  	if pr.pStart > 0 {
   130  		ports = append(ports, pr.String())
   131  	}
   132  	return strings.Join(ports, ", ")
   133  }
   134  
   135  func statusAction(instanceName string) error {
   136  	cli, err := getCli()
   137  	if err != nil {
   138  		return err
   139  	}
   140  	services, _ := runningServices(cli, instanceName)
   141  	if err := printServices(cli.Out(), services); err != nil {
   142  		return err
   143  	}
   144  	return nil
   145  }
   146  
   147  func statusJSONAction(instanceName string) error {
   148  	cli, err := getCli()
   149  	if err != nil {
   150  		return err
   151  	}
   152  	services, _ := runningServices(cli, instanceName)
   153  	js, err := json.MarshalIndent(services, "", "    ")
   154  	if err != nil {
   155  		return err
   156  	}
   157  	fmt.Fprintln(cli.Out(), string(js))
   158  	return nil
   159  }
   160  
   161  func getCli() (command.Cli, error) {
   162  	cli, err := setupDockerContext()
   163  	if err != nil {
   164  		return nil, errors.Wrap(err, "unable to restore docker context")
   165  	}
   166  	return cli, nil
   167  }
   168  
   169  func runningServices(cli command.Cli, instanceName string) ([]swarmtypes.Service, error) {
   170  	orchestratorRaw := os.Getenv(internal.DockerStackOrchestratorEnvVar)
   171  	orchestrator, err := cli.StackOrchestrator(orchestratorRaw)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	return stack.GetServices(cli, getFlagset(orchestrator), orchestrator, options.Services{
   176  		Filter:    opts.NewFilterOpt(),
   177  		Namespace: instanceName,
   178  	})
   179  }
   180  
   181  func printServices(out io.Writer, services []swarmtypes.Service) error {
   182  	w := tabwriter.NewWriter(out, 20, 2, 3, ' ', 0)
   183  	printHeaders(w)
   184  
   185  	for _, service := range services {
   186  		printValues(w, &service)
   187  	}
   188  	return w.Flush()
   189  }
   190  
   191  func printHeaders(w io.Writer) {
   192  	var headers []string
   193  	for _, column := range listColumns {
   194  		headers = append(headers, column.header)
   195  	}
   196  	fmt.Fprintln(w, strings.Join(headers, "\t"))
   197  }
   198  
   199  func printValues(w io.Writer, service *swarmtypes.Service) {
   200  	var values []string
   201  	for _, column := range listColumns {
   202  		values = append(values, column.value(service))
   203  	}
   204  	fmt.Fprintln(w, strings.Join(values, "\t"))
   205  }