github.com/hernad/nomad@v1.6.112/command/acl_bootstrap.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hernad/nomad/api"
    14  	"github.com/mitchellh/cli"
    15  	"github.com/posener/complete"
    16  )
    17  
    18  type ACLBootstrapCommand struct {
    19  	Meta
    20  }
    21  
    22  func (c *ACLBootstrapCommand) Help() string {
    23  	helpText := `
    24  Usage: nomad acl bootstrap [options]
    25  
    26    Bootstrap is used to bootstrap the ACL system and get an initial token.
    27  
    28  General Options:
    29  
    30    ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
    31  
    32  Bootstrap Options:
    33  
    34    -json
    35      Output the bootstrap response in JSON format.
    36  
    37    -t
    38      Format and display the bootstrap response using a Go template.
    39  
    40  `
    41  	return strings.TrimSpace(helpText)
    42  }
    43  
    44  func (c *ACLBootstrapCommand) AutocompleteFlags() complete.Flags {
    45  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
    46  		complete.Flags{
    47  			"-json": complete.PredictNothing,
    48  			"-t":    complete.PredictAnything,
    49  		})
    50  }
    51  
    52  func (c *ACLBootstrapCommand) AutocompleteArgs() complete.Predictor {
    53  	return complete.PredictNothing
    54  }
    55  
    56  func (c *ACLBootstrapCommand) Synopsis() string {
    57  	return "Bootstrap the ACL system for initial token"
    58  }
    59  
    60  func (c *ACLBootstrapCommand) Name() string { return "acl bootstrap" }
    61  
    62  func (c *ACLBootstrapCommand) Run(args []string) int {
    63  
    64  	var (
    65  		json bool
    66  		tmpl string
    67  		file string
    68  	)
    69  
    70  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
    71  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    72  	flags.BoolVar(&json, "json", false, "")
    73  	flags.StringVar(&tmpl, "t", "", "")
    74  	if err := flags.Parse(args); err != nil {
    75  		return 1
    76  	}
    77  
    78  	// Check that we got no arguments
    79  	args = flags.Args()
    80  	if l := len(args); l < 0 || l > 1 {
    81  		c.Ui.Error("This command takes up to one argument")
    82  		c.Ui.Error(commandErrorText(c))
    83  		return 1
    84  	}
    85  
    86  	var terminalToken []byte
    87  	var err error
    88  
    89  	if len(args) == 1 {
    90  		switch args[0] {
    91  		case "":
    92  			terminalToken = []byte{}
    93  		case "-":
    94  			terminalToken, err = io.ReadAll(os.Stdin)
    95  		default:
    96  			file = args[0]
    97  			terminalToken, err = os.ReadFile(file)
    98  		}
    99  		if err != nil {
   100  			c.Ui.Error(fmt.Sprintf("Error reading provided token: %v", err))
   101  			return 1
   102  		}
   103  	}
   104  
   105  	// Remove newline from the token if it was passed by stdin
   106  	boottoken := strings.TrimSuffix(string(terminalToken), "\n")
   107  
   108  	// Get the HTTP client
   109  	client, err := c.Meta.Client()
   110  	if err != nil {
   111  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   112  		return 1
   113  	}
   114  
   115  	// Get the bootstrap token
   116  	token, _, err := client.ACLTokens().BootstrapOpts(boottoken, nil)
   117  	if err != nil {
   118  		c.Ui.Error(fmt.Sprintf("Error bootstrapping: %s", err))
   119  		return 1
   120  	}
   121  
   122  	if json || len(tmpl) > 0 {
   123  		out, err := Format(json, tmpl, token)
   124  		if err != nil {
   125  			c.Ui.Error(err.Error())
   126  			return 1
   127  		}
   128  
   129  		c.Ui.Output(out)
   130  		return 0
   131  	}
   132  
   133  	// Format the output
   134  	outputACLToken(c.Ui, token)
   135  	return 0
   136  }
   137  
   138  // formatACLPolicy returns formatted policy
   139  func formatACLPolicy(policy *api.ACLPolicy) string {
   140  	output := []string{
   141  		fmt.Sprintf("Name|%s", policy.Name),
   142  		fmt.Sprintf("Description|%s", policy.Description),
   143  		fmt.Sprintf("CreateIndex|%v", policy.CreateIndex),
   144  		fmt.Sprintf("ModifyIndex|%v", policy.ModifyIndex),
   145  	}
   146  
   147  	formattedOut := formatKV(output)
   148  
   149  	if policy.JobACL != nil {
   150  		output := []string{
   151  			fmt.Sprintf("Namespace|%v", policy.JobACL.Namespace),
   152  			fmt.Sprintf("JobID|%v", policy.JobACL.JobID),
   153  			fmt.Sprintf("Group|%v", policy.JobACL.Group),
   154  			fmt.Sprintf("Task|%v", policy.JobACL.Task),
   155  		}
   156  		formattedOut += "\n\n[bold]Associated Workload[reset]\n"
   157  		formattedOut += formatKV(output)
   158  	}
   159  
   160  	// these are potentially large blobs so leave till the end
   161  	formattedOut += "\n\n[bold]Rules[reset]\n\n"
   162  	formattedOut += policy.Rules
   163  
   164  	return formattedOut
   165  }
   166  
   167  // outputACLToken formats and outputs the ACL token via the UI in the correct
   168  // format.
   169  func outputACLToken(ui cli.Ui, token *api.ACLToken) {
   170  
   171  	// Build the initial KV output which is always the same not matter whether
   172  	// the token is a management or client type.
   173  	kvOutput := []string{
   174  		fmt.Sprintf("Accessor ID|%s", token.AccessorID),
   175  		fmt.Sprintf("Secret ID|%s", token.SecretID),
   176  		fmt.Sprintf("Name|%s", token.Name),
   177  		fmt.Sprintf("Type|%s", token.Type),
   178  		fmt.Sprintf("Global|%v", token.Global),
   179  		fmt.Sprintf("Create Time|%v", token.CreateTime),
   180  		fmt.Sprintf("Expiry Time |%s", expiryTimeString(token.ExpirationTime)),
   181  		fmt.Sprintf("Create Index|%d", token.CreateIndex),
   182  		fmt.Sprintf("Modify Index|%d", token.ModifyIndex),
   183  	}
   184  
   185  	// If the token is a management type, make it obvious that it is not
   186  	// possible to have policies or roles assigned to it and just output the
   187  	// KV data.
   188  	if token.Type == "management" {
   189  		kvOutput = append(kvOutput, "Policies|n/a", "Roles|n/a")
   190  		ui.Output(formatKV(kvOutput))
   191  	} else {
   192  
   193  		// Policies are only currently referenced by name, so keep the previous
   194  		// format. When/if policies gain an ID alongside name like roles, this
   195  		// output should follow that of the roles.
   196  		kvOutput = append(kvOutput, fmt.Sprintf("Policies|%v", token.Policies))
   197  
   198  		var roleOutput []string
   199  
   200  		// If we have linked roles, add the ID and name in a list format to the
   201  		// output. Otherwise, make it clear there are no linked roles.
   202  		if len(token.Roles) > 0 {
   203  			roleOutput = append(roleOutput, "ID|Name")
   204  			for _, roleLink := range token.Roles {
   205  				roleOutput = append(roleOutput, roleLink.ID+"|"+roleLink.Name)
   206  			}
   207  		} else {
   208  			roleOutput = append(roleOutput, "<none>")
   209  		}
   210  
   211  		// Output the mixed formats of data, ensuring there is a space between
   212  		// the KV and list data.
   213  		ui.Output(formatKV(kvOutput))
   214  		ui.Output("")
   215  		ui.Output(fmt.Sprintf("Roles\n%s", formatList(roleOutput)))
   216  	}
   217  }
   218  
   219  func expiryTimeString(t *time.Time) string {
   220  	if t == nil || t.IsZero() {
   221  		return "<none>"
   222  	}
   223  	return t.String()
   224  }