mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
In Nomad Enterprise when multiple Vault/Consul clusters are configured, cluster admins can control access to clusters for jobs via namespace ACLs, similar to how we've done so for node pools. This changeset updates the ACL configuration structs, but doesn't wire them up.
247 lines
6.4 KiB
Go
247 lines
6.4 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
type NamespaceStatusCommand struct {
|
|
Meta
|
|
}
|
|
|
|
func (c *NamespaceStatusCommand) Help() string {
|
|
helpText := `
|
|
Usage: nomad namespace status [options] <namespace>
|
|
|
|
Status is used to view the status of a particular namespace.
|
|
|
|
If ACLs are enabled, this command requires a management ACL token or a token
|
|
that has a capability associated with the namespace.
|
|
|
|
General Options:
|
|
|
|
` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
|
|
|
|
Status Specific Options:
|
|
|
|
-json
|
|
Output the latest namespace status information in a JSON format.
|
|
|
|
-t
|
|
Format and display namespace status information using a Go template.
|
|
`
|
|
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *NamespaceStatusCommand) AutocompleteFlags() complete.Flags {
|
|
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
|
complete.Flags{
|
|
"-json": complete.PredictNothing,
|
|
"-t": complete.PredictAnything,
|
|
})
|
|
}
|
|
|
|
func (c *NamespaceStatusCommand) AutocompleteArgs() complete.Predictor {
|
|
return NamespacePredictor(c.Meta.Client, nil)
|
|
}
|
|
|
|
func (c *NamespaceStatusCommand) Synopsis() string {
|
|
return "Display a namespace's status"
|
|
}
|
|
|
|
func (c *NamespaceStatusCommand) Name() string { return "namespace status" }
|
|
|
|
func (c *NamespaceStatusCommand) Run(args []string) int {
|
|
var json bool
|
|
var tmpl string
|
|
|
|
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
|
flags.BoolVar(&json, "json", false, "")
|
|
flags.StringVar(&tmpl, "t", "", "")
|
|
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
|
|
|
if err := flags.Parse(args); err != nil {
|
|
return 1
|
|
}
|
|
|
|
// Check that we got one arguments
|
|
args = flags.Args()
|
|
if l := len(args); l != 1 {
|
|
c.Ui.Error("This command takes one argument: <namespace>")
|
|
c.Ui.Error(commandErrorText(c))
|
|
return 1
|
|
}
|
|
|
|
name := args[0]
|
|
|
|
// Get the HTTP client
|
|
client, err := c.Meta.Client()
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
|
return 1
|
|
}
|
|
|
|
// Do a prefix lookup
|
|
ns, possible, err := getNamespace(client.Namespaces(), name)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error retrieving namespaces: %s", err))
|
|
return 1
|
|
}
|
|
|
|
if len(possible) != 0 {
|
|
c.Ui.Error(fmt.Sprintf("Prefix matched multiple namespaces\n\n%s", formatNamespaces(possible)))
|
|
return 1
|
|
}
|
|
|
|
if json || len(tmpl) > 0 {
|
|
out, err := Format(json, tmpl, ns)
|
|
if err != nil {
|
|
c.Ui.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
c.Ui.Output(out)
|
|
return 0
|
|
}
|
|
|
|
c.Ui.Output(formatNamespaceBasics(ns))
|
|
|
|
if len(ns.Meta) > 0 {
|
|
c.Ui.Output(c.Colorize().Color("\n[bold]Metadata[reset]"))
|
|
var meta []string
|
|
for k := range ns.Meta {
|
|
meta = append(meta, fmt.Sprintf("%s|%s", k, ns.Meta[k]))
|
|
}
|
|
sort.Strings(meta)
|
|
c.Ui.Output(formatKV(meta))
|
|
}
|
|
|
|
if ns.Quota != "" {
|
|
quotas := client.Quotas()
|
|
spec, _, err := quotas.Info(ns.Quota, nil)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error retrieving quota spec: %s", err))
|
|
return 1
|
|
}
|
|
|
|
// Get the quota usages
|
|
usages, failures := quotaUsages(spec, quotas)
|
|
|
|
// Format the limits
|
|
c.Ui.Output(c.Colorize().Color("\n[bold]Quota Limits[reset]"))
|
|
c.Ui.Output(formatQuotaLimits(spec, usages))
|
|
|
|
// Display any failures
|
|
if len(failures) != 0 {
|
|
c.Ui.Error(c.Colorize().Color("\n[bold][red]Lookup Failures[reset]"))
|
|
for region, failure := range failures {
|
|
c.Ui.Error(fmt.Sprintf(" * Failed to retrieve quota usage for region %q: %v", region, failure))
|
|
return 1
|
|
}
|
|
}
|
|
}
|
|
|
|
if ns.NodePoolConfiguration != nil {
|
|
c.Ui.Output(c.Colorize().Color("\n[bold]Node Pool Configuration[reset]"))
|
|
npConfig := ns.NodePoolConfiguration
|
|
npConfigOut := []string{
|
|
fmt.Sprintf("Default|%s", npConfig.Default),
|
|
}
|
|
if len(npConfig.Allowed) > 0 {
|
|
npConfigOut = append(npConfigOut, fmt.Sprintf("Allowed|%s", strings.Join(npConfig.Allowed, ", ")))
|
|
}
|
|
if len(npConfig.Denied) > 0 {
|
|
npConfigOut = append(npConfigOut, fmt.Sprintf("Denied|%s", strings.Join(npConfig.Denied, ", ")))
|
|
}
|
|
c.Ui.Output(formatKV(npConfigOut))
|
|
}
|
|
|
|
if ns.VaultConfiguration != nil {
|
|
c.Ui.Output(c.Colorize().Color("\n[bold]Vault Configuration[reset]"))
|
|
vConfig := ns.VaultConfiguration
|
|
vConfigOut := []string{
|
|
fmt.Sprintf("Default|%s", vConfig.Default),
|
|
}
|
|
if len(vConfig.Allowed) > 0 {
|
|
vConfigOut = append(vConfigOut, fmt.Sprintf("Allowed|%s", strings.Join(vConfig.Allowed, ", ")))
|
|
}
|
|
if len(vConfig.Denied) > 0 {
|
|
vConfigOut = append(vConfigOut, fmt.Sprintf("Denied|%s", strings.Join(vConfig.Denied, ", ")))
|
|
}
|
|
c.Ui.Output(formatKV(vConfigOut))
|
|
}
|
|
|
|
if ns.ConsulConfiguration != nil {
|
|
c.Ui.Output(c.Colorize().Color("\n[bold]Consul Configuration[reset]"))
|
|
cConfig := ns.ConsulConfiguration
|
|
cConfigOut := []string{
|
|
fmt.Sprintf("Default|%s", cConfig.Default),
|
|
}
|
|
if len(cConfig.Allowed) > 0 {
|
|
cConfigOut = append(cConfigOut, fmt.Sprintf("Allowed|%s", strings.Join(cConfig.Allowed, ", ")))
|
|
}
|
|
if len(cConfig.Denied) > 0 {
|
|
cConfigOut = append(cConfigOut, fmt.Sprintf("Denied|%s", strings.Join(cConfig.Denied, ", ")))
|
|
}
|
|
c.Ui.Output(formatKV(cConfigOut))
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// formatNamespaceBasics formats the basic information of the namespace
|
|
func formatNamespaceBasics(ns *api.Namespace) string {
|
|
enabled_drivers := "*"
|
|
disabled_drivers := ""
|
|
if ns.Capabilities != nil {
|
|
if len(ns.Capabilities.EnabledTaskDrivers) != 0 {
|
|
enabled_drivers = strings.Join(ns.Capabilities.EnabledTaskDrivers, ",")
|
|
}
|
|
if len(ns.Capabilities.DisabledTaskDrivers) != 0 {
|
|
disabled_drivers = strings.Join(ns.Capabilities.DisabledTaskDrivers, ",")
|
|
}
|
|
}
|
|
basic := []string{
|
|
fmt.Sprintf("Name|%s", ns.Name),
|
|
fmt.Sprintf("Description|%s", ns.Description),
|
|
fmt.Sprintf("Quota|%s", ns.Quota),
|
|
fmt.Sprintf("EnabledDrivers|%s", enabled_drivers),
|
|
fmt.Sprintf("DisabledDrivers|%s", disabled_drivers),
|
|
}
|
|
|
|
return formatKV(basic)
|
|
}
|
|
|
|
func getNamespace(client *api.Namespaces, ns string) (match *api.Namespace, possible []*api.Namespace, err error) {
|
|
// Do a prefix lookup
|
|
namespaces, _, err := client.PrefixList(ns, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
l := len(namespaces)
|
|
switch {
|
|
case l == 0:
|
|
return nil, nil, fmt.Errorf("Namespace %q matched no namespaces", ns)
|
|
case l == 1:
|
|
return namespaces[0], nil, nil
|
|
default:
|
|
// search for an exact match in the returned namespaces
|
|
for _, namespace := range namespaces {
|
|
if namespace.Name == ns {
|
|
return namespace, nil, nil
|
|
}
|
|
}
|
|
// if not found, return the fuzzy matches.
|
|
return nil, namespaces, nil
|
|
}
|
|
}
|