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 }