2023-04-10 15:36:59 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-03-21 10:59:03 +00:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
|
|
"github.com/mitchellh/cli"
|
|
|
|
"github.com/posener/complete"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Ensure ServiceListCommand satisfies the cli.Command interface.
|
|
|
|
var _ cli.Command = &ServiceListCommand{}
|
|
|
|
|
|
|
|
// ServiceListCommand implements cli.Command.
|
|
|
|
type ServiceListCommand struct {
|
|
|
|
Meta
|
|
|
|
}
|
|
|
|
|
|
|
|
// Help satisfies the cli.Command Help function.
|
|
|
|
func (s *ServiceListCommand) Help() string {
|
|
|
|
helpText := `
|
|
|
|
Usage: nomad service list [options]
|
|
|
|
|
|
|
|
List is used to list the currently registered services.
|
|
|
|
|
|
|
|
If ACLs are enabled, this command requires a token with the 'read-job'
|
|
|
|
capabilities for the namespace of all services. Any namespaces that the token
|
|
|
|
does not have access to will have its services filtered from the results.
|
|
|
|
|
|
|
|
General Options:
|
|
|
|
|
|
|
|
` + generalOptionsUsage(usageOptsDefault) + `
|
|
|
|
|
|
|
|
Service List Options:
|
|
|
|
|
|
|
|
-json
|
|
|
|
Output the services in JSON format.
|
|
|
|
|
|
|
|
-t
|
|
|
|
Format and display the services using a Go template.
|
|
|
|
`
|
|
|
|
return strings.TrimSpace(helpText)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Synopsis satisfies the cli.Command Synopsis function.
|
|
|
|
func (s *ServiceListCommand) Synopsis() string {
|
|
|
|
return "Display all registered Nomad services"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ServiceListCommand) AutocompleteFlags() complete.Flags {
|
|
|
|
return mergeAutocompleteFlags(s.Meta.AutocompleteFlags(FlagSetClient),
|
|
|
|
complete.Flags{
|
|
|
|
"-json": complete.PredictNothing,
|
|
|
|
"-t": complete.PredictAnything,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the name of this command.
|
|
|
|
func (s *ServiceListCommand) Name() string { return "service list" }
|
|
|
|
|
|
|
|
// Run satisfies the cli.Command Run function.
|
|
|
|
func (s *ServiceListCommand) Run(args []string) int {
|
|
|
|
|
|
|
|
var (
|
|
|
|
json bool
|
|
|
|
tmpl, name string
|
|
|
|
)
|
|
|
|
|
|
|
|
flags := s.Meta.FlagSet(s.Name(), FlagSetClient)
|
|
|
|
flags.Usage = func() { s.Ui.Output(s.Help()) }
|
|
|
|
flags.BoolVar(&json, "json", false, "")
|
|
|
|
flags.StringVar(&name, "name", "", "")
|
|
|
|
flags.StringVar(&tmpl, "t", "", "")
|
|
|
|
if err := flags.Parse(args); err != nil {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if args = flags.Args(); len(args) > 0 {
|
|
|
|
s.Ui.Error("This command takes no arguments")
|
|
|
|
s.Ui.Error(commandErrorText(s))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := s.Meta.Client()
|
|
|
|
if err != nil {
|
|
|
|
s.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2022-03-24 08:08:45 +00:00
|
|
|
list, _, err := client.Services().List(nil)
|
2022-03-21 10:59:03 +00:00
|
|
|
if err != nil {
|
|
|
|
s.Ui.Error(fmt.Sprintf("Error listing service registrations: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(list) == 0 {
|
|
|
|
s.Ui.Output("No service registrations found")
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if json || len(tmpl) > 0 {
|
|
|
|
out, err := Format(json, tmpl, list)
|
|
|
|
if err != nil {
|
|
|
|
s.Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
s.Ui.Output(out)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
s.formatOutput(list)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ServiceListCommand) formatOutput(regs []*api.ServiceRegistrationListStub) {
|
|
|
|
|
|
|
|
// Create objects to hold sorted a sorted namespace array and a mapping, so
|
|
|
|
// we can perform service lookups on a namespace basis.
|
|
|
|
sortedNamespaces := make([]string, len(regs))
|
|
|
|
namespacedServices := make(map[string][]*api.ServiceRegistrationStub)
|
|
|
|
|
|
|
|
for i, namespaceServices := range regs {
|
|
|
|
sortedNamespaces[i] = namespaceServices.Namespace
|
|
|
|
namespacedServices[namespaceServices.Namespace] = namespaceServices.Services
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort the namespaces.
|
|
|
|
sort.Strings(sortedNamespaces)
|
|
|
|
|
|
|
|
// The table always starts with the service name.
|
|
|
|
outputTable := []string{"Service Name"}
|
|
|
|
|
|
|
|
// If the request was made using the wildcard namespace, include this in
|
|
|
|
// the output.
|
|
|
|
if s.Meta.namespace == api.AllNamespacesNamespace {
|
|
|
|
outputTable[0] += "|Namespace"
|
|
|
|
}
|
|
|
|
|
|
|
|
// The tags come last and are always present.
|
|
|
|
outputTable[0] += "|Tags"
|
|
|
|
|
|
|
|
for _, ns := range sortedNamespaces {
|
|
|
|
|
|
|
|
// Grab the services belonging to this namespace.
|
|
|
|
services := namespacedServices[ns]
|
|
|
|
|
|
|
|
// Create objects to hold sorted a sorted service name array and a
|
|
|
|
// mapping, so we can perform service tag lookups on a name basis.
|
|
|
|
sortedNames := make([]string, len(services))
|
|
|
|
serviceTags := make(map[string][]string)
|
|
|
|
|
|
|
|
for i, service := range services {
|
|
|
|
sortedNames[i] = service.ServiceName
|
|
|
|
serviceTags[service.ServiceName] = service.Tags
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort the service names.
|
|
|
|
sort.Strings(sortedNames)
|
|
|
|
|
|
|
|
for _, serviceName := range sortedNames {
|
|
|
|
|
|
|
|
// Grab the service tags, and sort these for good measure.
|
|
|
|
tags := serviceTags[serviceName]
|
|
|
|
sort.Strings(tags)
|
|
|
|
|
|
|
|
// Build the output array entry.
|
|
|
|
regOutput := serviceName
|
|
|
|
|
|
|
|
if s.Meta.namespace == api.AllNamespacesNamespace {
|
|
|
|
regOutput += "|" + ns
|
|
|
|
}
|
|
|
|
regOutput += "|" + fmt.Sprintf("[%s]", strings.Join(tags, ","))
|
|
|
|
outputTable = append(outputTable, regOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Ui.Output(formatList(outputTable))
|
|
|
|
}
|