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 }