Add optional JSON format to the ACL CLI commands output (#7198)
* Add ACL CLI commands output format option. Add command level formatter, that incapsulates command output printing logiс that depends on the command `-format` option. Move Print* functions from acl_helpers to prettyFormatter. Add jsonFormatter. * Return error code in case of formatting failure. * Add acl commands -format option to doc.
This commit is contained in:
commit
e873dbe111
|
@ -1,257 +1,13 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func PrintToken(token *api.ACLToken, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("AccessorID: %s", token.AccessorID))
|
||||
ui.Info(fmt.Sprintf("SecretID: %s", token.SecretID))
|
||||
if token.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf("Namespace: %s", token.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Description: %s", token.Description))
|
||||
ui.Info(fmt.Sprintf("Local: %t", token.Local))
|
||||
ui.Info(fmt.Sprintf("Create Time: %v", token.CreateTime))
|
||||
if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() {
|
||||
ui.Info(fmt.Sprintf("Expiration Time: %v", *token.ExpirationTime))
|
||||
}
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf("Hash: %x", token.Hash))
|
||||
ui.Info(fmt.Sprintf("Create Index: %d", token.CreateIndex))
|
||||
ui.Info(fmt.Sprintf("Modify Index: %d", token.ModifyIndex))
|
||||
}
|
||||
if len(token.Policies) > 0 {
|
||||
ui.Info(fmt.Sprintf("Policies:"))
|
||||
for _, policy := range token.Policies {
|
||||
ui.Info(fmt.Sprintf(" %s - %s", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(token.Roles) > 0 {
|
||||
ui.Info(fmt.Sprintf("Roles:"))
|
||||
for _, role := range token.Roles {
|
||||
ui.Info(fmt.Sprintf(" %s - %s", role.ID, role.Name))
|
||||
}
|
||||
}
|
||||
if len(token.ServiceIdentities) > 0 {
|
||||
ui.Info(fmt.Sprintf("Service Identities:"))
|
||||
for _, svcid := range token.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: %s)", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: all)", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
if token.Rules != "" {
|
||||
ui.Info(fmt.Sprintf("Rules:"))
|
||||
ui.Info(token.Rules)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintTokenListEntry(token *api.ACLTokenListEntry, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("AccessorID: %s", token.AccessorID))
|
||||
if token.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf("Namespace: %s", token.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Description: %s", token.Description))
|
||||
ui.Info(fmt.Sprintf("Local: %t", token.Local))
|
||||
ui.Info(fmt.Sprintf("Create Time: %v", token.CreateTime))
|
||||
if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() {
|
||||
ui.Info(fmt.Sprintf("Expiration Time: %v", *token.ExpirationTime))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Legacy: %t", token.Legacy))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf("Hash: %x", token.Hash))
|
||||
ui.Info(fmt.Sprintf("Create Index: %d", token.CreateIndex))
|
||||
ui.Info(fmt.Sprintf("Modify Index: %d", token.ModifyIndex))
|
||||
}
|
||||
if len(token.Policies) > 0 {
|
||||
ui.Info(fmt.Sprintf("Policies:"))
|
||||
for _, policy := range token.Policies {
|
||||
ui.Info(fmt.Sprintf(" %s - %s", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(token.Roles) > 0 {
|
||||
ui.Info(fmt.Sprintf("Roles:"))
|
||||
for _, role := range token.Roles {
|
||||
ui.Info(fmt.Sprintf(" %s - %s", role.ID, role.Name))
|
||||
}
|
||||
}
|
||||
if len(token.ServiceIdentities) > 0 {
|
||||
ui.Info(fmt.Sprintf("Service Identities:"))
|
||||
for _, svcid := range token.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: %s)", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: all)", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintPolicy(policy *api.ACLPolicy, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("ID: %s", policy.ID))
|
||||
ui.Info(fmt.Sprintf("Name: %s", policy.Name))
|
||||
if policy.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf("Namespace: %s", policy.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Description: %s", policy.Description))
|
||||
ui.Info(fmt.Sprintf("Datacenters: %s", strings.Join(policy.Datacenters, ", ")))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf("Hash: %x", policy.Hash))
|
||||
ui.Info(fmt.Sprintf("Create Index: %d", policy.CreateIndex))
|
||||
ui.Info(fmt.Sprintf("Modify Index: %d", policy.ModifyIndex))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Rules:"))
|
||||
ui.Info(policy.Rules)
|
||||
}
|
||||
|
||||
func PrintPolicyListEntry(policy *api.ACLPolicyListEntry, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("%s:", policy.Name))
|
||||
ui.Info(fmt.Sprintf(" ID: %s", policy.ID))
|
||||
if policy.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf(" Namespace: %s", policy.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf(" Description: %s", policy.Description))
|
||||
ui.Info(fmt.Sprintf(" Datacenters: %s", strings.Join(policy.Datacenters, ", ")))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf(" Hash: %x", policy.Hash))
|
||||
ui.Info(fmt.Sprintf(" Create Index: %d", policy.CreateIndex))
|
||||
ui.Info(fmt.Sprintf(" Modify Index: %d", policy.ModifyIndex))
|
||||
}
|
||||
}
|
||||
|
||||
func PrintRole(role *api.ACLRole, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("ID: %s", role.ID))
|
||||
ui.Info(fmt.Sprintf("Name: %s", role.Name))
|
||||
if role.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf("Namespace: %s", role.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Description: %s", role.Description))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf("Hash: %x", role.Hash))
|
||||
ui.Info(fmt.Sprintf("Create Index: %d", role.CreateIndex))
|
||||
ui.Info(fmt.Sprintf("Modify Index: %d", role.ModifyIndex))
|
||||
}
|
||||
if len(role.Policies) > 0 {
|
||||
ui.Info(fmt.Sprintf("Policies:"))
|
||||
for _, policy := range role.Policies {
|
||||
ui.Info(fmt.Sprintf(" %s - %s", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(role.ServiceIdentities) > 0 {
|
||||
ui.Info(fmt.Sprintf("Service Identities:"))
|
||||
for _, svcid := range role.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: %s)", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: all)", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintRoleListEntry(role *api.ACLRole, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("%s:", role.Name))
|
||||
ui.Info(fmt.Sprintf(" ID: %s", role.ID))
|
||||
if role.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf(" Namespace: %s", role.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf(" Description: %s", role.Description))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf(" Hash: %x", role.Hash))
|
||||
ui.Info(fmt.Sprintf(" Create Index: %d", role.CreateIndex))
|
||||
ui.Info(fmt.Sprintf(" Modify Index: %d", role.ModifyIndex))
|
||||
}
|
||||
if len(role.Policies) > 0 {
|
||||
ui.Info(fmt.Sprintf(" Policies:"))
|
||||
for _, policy := range role.Policies {
|
||||
ui.Info(fmt.Sprintf(" %s - %s", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(role.ServiceIdentities) > 0 {
|
||||
ui.Info(fmt.Sprintf(" Service Identities:"))
|
||||
for _, svcid := range role.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: %s)", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
ui.Info(fmt.Sprintf(" %s (Datacenters: all)", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintAuthMethod(method *api.ACLAuthMethod, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("Name: %s", method.Name))
|
||||
ui.Info(fmt.Sprintf("Type: %s", method.Type))
|
||||
if method.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf("Namespace: %s", method.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Description: %s", method.Description))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf("Create Index: %d", method.CreateIndex))
|
||||
ui.Info(fmt.Sprintf("Modify Index: %d", method.ModifyIndex))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("Config:"))
|
||||
output, err := json.MarshalIndent(method.Config, "", " ")
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error formatting auth method configuration: %s", err))
|
||||
}
|
||||
ui.Output(string(output))
|
||||
}
|
||||
|
||||
func PrintAuthMethodListEntry(method *api.ACLAuthMethodListEntry, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("%s:", method.Name))
|
||||
ui.Info(fmt.Sprintf(" Type: %s", method.Type))
|
||||
if method.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf(" Namespace: %s", method.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf(" Description: %s", method.Description))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf(" Create Index: %d", method.CreateIndex))
|
||||
ui.Info(fmt.Sprintf(" Modify Index: %d", method.ModifyIndex))
|
||||
}
|
||||
}
|
||||
|
||||
func PrintBindingRule(rule *api.ACLBindingRule, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("ID: %s", rule.ID))
|
||||
if rule.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf("Namespace: %s", rule.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf("AuthMethod: %s", rule.AuthMethod))
|
||||
ui.Info(fmt.Sprintf("Description: %s", rule.Description))
|
||||
ui.Info(fmt.Sprintf("BindType: %s", rule.BindType))
|
||||
ui.Info(fmt.Sprintf("BindName: %s", rule.BindName))
|
||||
ui.Info(fmt.Sprintf("Selector: %s", rule.Selector))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf("Create Index: %d", rule.CreateIndex))
|
||||
ui.Info(fmt.Sprintf("Modify Index: %d", rule.ModifyIndex))
|
||||
}
|
||||
}
|
||||
|
||||
func PrintBindingRuleListEntry(rule *api.ACLBindingRule, ui cli.Ui, showMeta bool) {
|
||||
ui.Info(fmt.Sprintf("%s:", rule.ID))
|
||||
if rule.Namespace != "" {
|
||||
ui.Info(fmt.Sprintf(" Namespace: %s", rule.Namespace))
|
||||
}
|
||||
ui.Info(fmt.Sprintf(" AuthMethod: %s", rule.AuthMethod))
|
||||
ui.Info(fmt.Sprintf(" Description: %s", rule.Description))
|
||||
ui.Info(fmt.Sprintf(" BindType: %s", rule.BindType))
|
||||
ui.Info(fmt.Sprintf(" BindName: %s", rule.BindName))
|
||||
ui.Info(fmt.Sprintf(" Selector: %s", rule.Selector))
|
||||
if showMeta {
|
||||
ui.Info(fmt.Sprintf(" Create Index: %d", rule.CreateIndex))
|
||||
ui.Info(fmt.Sprintf(" Modify Index: %d", rule.ModifyIndex))
|
||||
}
|
||||
}
|
||||
|
||||
func GetTokenIDFromPartial(client *api.Client, partialID string) (string, error) {
|
||||
if partialID == "anonymous" {
|
||||
return structs.ACLTokenAnonymousID, nil
|
||||
|
|
|
@ -4,9 +4,10 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/authmethod"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/hashicorp/consul/command/helpers"
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -33,6 +34,7 @@ type cmd struct {
|
|||
k8sServiceAccountJWT string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
|
||||
testStdin io.Reader
|
||||
}
|
||||
|
@ -90,6 +92,12 @@ func (c *cmd) init() {
|
|||
"validate other JWTs during login. "+
|
||||
"This flag is required for type=kubernetes.",
|
||||
)
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
authmethod.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(authmethod.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -159,7 +167,21 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
acl.PrintAuthMethod(method, c.UI, c.showMeta)
|
||||
formatter, err := authmethod.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatAuthMethod(method)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package authmethodcreate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
// activate testing auth method
|
||||
|
@ -107,6 +109,64 @@ func TestAuthMethodCreateCommand(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAuthMethodCreateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
t.Run("type required", func(t *testing.T) {
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 1)
|
||||
require.Contains(t, ui.ErrorWriter.String(), "Missing required '-type' flag")
|
||||
})
|
||||
|
||||
t.Run("create testing", func(t *testing.T) {
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-type=testing",
|
||||
"-name=test",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
code := cmd.Run(args)
|
||||
out := ui.OutputWriter.String()
|
||||
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
require.Contains(t, out, "test")
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(out), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthMethodCreateCommand_k8s(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package authmethod
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
PrettyFormat string = "pretty"
|
||||
JSONFormat string = "json"
|
||||
)
|
||||
|
||||
// Formatter defines methods provided by authmethod command output formatter
|
||||
type Formatter interface {
|
||||
FormatAuthMethod(method *api.ACLAuthMethod) (string, error)
|
||||
FormatAuthMethodList(methods []*api.ACLAuthMethodListEntry) (string, error)
|
||||
}
|
||||
|
||||
// GetSupportedFormats returns supported formats
|
||||
func GetSupportedFormats() []string {
|
||||
return []string{PrettyFormat, JSONFormat}
|
||||
}
|
||||
|
||||
// NewFormatter returns Formatter implementation
|
||||
func NewFormatter(format string, showMeta bool) (formatter Formatter, err error) {
|
||||
switch format {
|
||||
case PrettyFormat:
|
||||
formatter = newPrettyFormatter(showMeta)
|
||||
case JSONFormat:
|
||||
formatter = newJSONFormatter(showMeta)
|
||||
default:
|
||||
err = fmt.Errorf("Unknown format: %s", format)
|
||||
}
|
||||
|
||||
return formatter, err
|
||||
}
|
||||
|
||||
func newPrettyFormatter(showMeta bool) Formatter {
|
||||
return &prettyFormatter{showMeta}
|
||||
}
|
||||
|
||||
type prettyFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatAuthMethod(method *api.ACLAuthMethod) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("Name: %s\n", method.Name))
|
||||
buffer.WriteString(fmt.Sprintf("Type: %s\n", method.Type))
|
||||
if method.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", method.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("Description: %s\n", method.Description))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", method.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", method.ModifyIndex))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintln("Config:"))
|
||||
output, err := json.MarshalIndent(method.Config, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error formatting auth method configuration: %s", err)
|
||||
}
|
||||
buffer.WriteString(string(output))
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatAuthMethodList(methods []*api.ACLAuthMethodListEntry) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, method := range methods {
|
||||
buffer.WriteString(f.formatAuthMethodListEntry(method))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) formatAuthMethodListEntry(method *api.ACLAuthMethodListEntry) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("%s:\n", method.Name))
|
||||
buffer.WriteString(fmt.Sprintf(" Type: %s\n", method.Type))
|
||||
if method.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf(" Namespace: %s\n", method.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" Description: %s\n", method.Description))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf(" Create Index: %d\n", method.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf(" Modify Index: %d\n", method.ModifyIndex))
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func newJSONFormatter(showMeta bool) Formatter {
|
||||
return &jsonFormatter{showMeta}
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatAuthMethod(method *api.ACLAuthMethod) (string, error) {
|
||||
b, err := json.MarshalIndent(method, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal authmethod:, %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatAuthMethodList(methods []*api.ACLAuthMethodListEntry) (string, error) {
|
||||
b, err := json.MarshalIndent(methods, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal authmethods:, %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
|
@ -3,8 +3,9 @@ package authmethodlist
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/authmethod"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -22,6 +23,7 @@ type cmd struct {
|
|||
help string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -34,6 +36,12 @@ func (c *cmd) init() {
|
|||
"Indicates that auth method metadata such "+
|
||||
"as the raft indices should be shown for each entry.",
|
||||
)
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
authmethod.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(authmethod.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -59,8 +67,19 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
for _, method := range methods {
|
||||
acl.PrintAuthMethodListEntry(method, c.UI, c.showMeta)
|
||||
formatter, err := authmethod.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatAuthMethodList(methods)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package authmethodlist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
// activate testing auth method
|
||||
|
@ -104,3 +106,73 @@ func TestAuthMethodListCommand(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthMethodListCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
client := a.Client()
|
||||
|
||||
createAuthMethod := func(t *testing.T) string {
|
||||
id, err := uuid.GenerateUUID()
|
||||
require.NoError(t, err)
|
||||
|
||||
methodName := "test-" + id
|
||||
|
||||
_, _, err = client.ACL().AuthMethodCreate(
|
||||
&api.ACLAuthMethod{
|
||||
Name: methodName,
|
||||
Type: "testing",
|
||||
Description: "test",
|
||||
},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
return methodName
|
||||
}
|
||||
|
||||
var methodNames []string
|
||||
for i := 0; i < 5; i++ {
|
||||
methodName := createAuthMethod(t)
|
||||
methodNames = append(methodNames, methodName)
|
||||
}
|
||||
|
||||
t.Run("found some", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
|
||||
for _, methodName := range methodNames {
|
||||
require.Contains(t, output, methodName)
|
||||
}
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ package authmethodread
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/authmethod"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -24,6 +25,7 @@ type cmd struct {
|
|||
name string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -44,6 +46,13 @@ func (c *cmd) init() {
|
|||
"The name of the auth method to read.",
|
||||
)
|
||||
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
authmethod.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(authmethod.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -75,7 +84,22 @@ func (c *cmd) Run(args []string) int {
|
|||
c.UI.Error(fmt.Sprintf("Auth method not found with name %q", c.name))
|
||||
return 1
|
||||
}
|
||||
acl.PrintAuthMethod(method, c.UI, c.showMeta)
|
||||
|
||||
formatter, err := authmethod.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatAuthMethod(method)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package authmethodread
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
// activate testing auth method
|
||||
|
@ -113,3 +115,68 @@ func TestAuthMethodReadCommand(t *testing.T) {
|
|||
require.Contains(t, output, name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthMethodReadCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
client := a.Client()
|
||||
|
||||
createAuthMethod := func(t *testing.T) string {
|
||||
id, err := uuid.GenerateUUID()
|
||||
require.NoError(t, err)
|
||||
|
||||
methodName := "test-" + id
|
||||
|
||||
_, _, err = client.ACL().AuthMethodCreate(
|
||||
&api.ACLAuthMethod{
|
||||
Name: methodName,
|
||||
Type: "testing",
|
||||
Description: "test",
|
||||
},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
return methodName
|
||||
}
|
||||
|
||||
t.Run("read by name", func(t *testing.T) {
|
||||
name := createAuthMethod(t)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=" + name,
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, name)
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/authmethod"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/hashicorp/consul/command/helpers"
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -34,6 +35,7 @@ type cmd struct {
|
|||
|
||||
noMerge bool
|
||||
showMeta bool
|
||||
format string
|
||||
|
||||
testStdin io.Reader
|
||||
}
|
||||
|
@ -90,7 +92,12 @@ func (c *cmd) init() {
|
|||
c.flags.BoolVar(&c.noMerge, "no-merge", false, "Do not merge the current auth method "+
|
||||
"information with what is provided to the command. Instead overwrite all fields "+
|
||||
"with the exception of the name which is immutable.")
|
||||
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
authmethod.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(authmethod.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -190,8 +197,21 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
c.UI.Info(fmt.Sprintf("Auth method updated successfully"))
|
||||
acl.PrintAuthMethod(method, c.UI, c.showMeta)
|
||||
formatter, err := authmethod.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatAuthMethod(method)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package authmethodupdate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -15,6 +16,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
// activate testing auth method
|
||||
|
@ -124,6 +126,94 @@ func TestAuthMethodUpdateCommand(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAuthMethodUpdateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
client := a.Client()
|
||||
|
||||
t.Run("update without name", func(t *testing.T) {
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 1)
|
||||
require.Contains(t, ui.ErrorWriter.String(), "Cannot update an auth method without specifying the -name parameter")
|
||||
})
|
||||
|
||||
createAuthMethod := func(t *testing.T) string {
|
||||
id, err := uuid.GenerateUUID()
|
||||
require.NoError(t, err)
|
||||
|
||||
methodName := "test-" + id
|
||||
|
||||
_, _, err = client.ACL().AuthMethodCreate(
|
||||
&api.ACLAuthMethod{
|
||||
Name: methodName,
|
||||
Type: "testing",
|
||||
Description: "test",
|
||||
},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
return methodName
|
||||
}
|
||||
|
||||
t.Run("update all fields", func(t *testing.T) {
|
||||
name := createAuthMethod(t)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=" + name,
|
||||
"-description", "updated description",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
method, _, err := client.ACL().AuthMethodRead(
|
||||
name,
|
||||
&api.QueryOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, method)
|
||||
require.Equal(t, "updated description", method.Description)
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthMethodUpdateCommand_noMerge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@ package bindingrulecreate
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/bindingrule"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -29,6 +30,7 @@ type cmd struct {
|
|||
bindName string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -75,6 +77,12 @@ func (c *cmd) init() {
|
|||
"Name to bind on match. Can use ${var} interpolation. "+
|
||||
"This flag is required.",
|
||||
)
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
bindingrule.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(bindingrule.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -122,7 +130,21 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
acl.PrintBindingRule(rule, c.UI, c.showMeta)
|
||||
formatter, err := bindingrule.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatBindingRule(rule)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package bindingrulecreate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
// activate testing auth method
|
||||
|
@ -173,3 +175,59 @@ func TestBindingRuleCreateCommand(t *testing.T) {
|
|||
require.Empty(t, ui.ErrorWriter.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestBindingRuleCreateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
client := a.Client()
|
||||
|
||||
// create an auth method in advance
|
||||
{
|
||||
_, _, err := client.ACL().AuthMethodCreate(
|
||||
&api.ACLAuthMethod{
|
||||
Name: "test",
|
||||
Type: "testing",
|
||||
},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Run("create it with no selector", func(t *testing.T) {
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-method=test",
|
||||
"-bind-type=service",
|
||||
"-bind-name=demo",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package bindingrule
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
PrettyFormat string = "pretty"
|
||||
JSONFormat string = "json"
|
||||
)
|
||||
|
||||
// Formatter defines methods provided by bindingrule command output formatter
|
||||
type Formatter interface {
|
||||
FormatBindingRule(rule *api.ACLBindingRule) (string, error)
|
||||
FormatBindingRuleList(rules []*api.ACLBindingRule) (string, error)
|
||||
}
|
||||
|
||||
// GetSupportedFormats returns supported formats
|
||||
func GetSupportedFormats() []string {
|
||||
return []string{PrettyFormat, JSONFormat}
|
||||
}
|
||||
|
||||
// NewFormatter returns Formatter implementation
|
||||
func NewFormatter(format string, showMeta bool) (formatter Formatter, err error) {
|
||||
switch format {
|
||||
case PrettyFormat:
|
||||
formatter = newPrettyFormatter(showMeta)
|
||||
case JSONFormat:
|
||||
formatter = newJSONFormatter(showMeta)
|
||||
default:
|
||||
err = fmt.Errorf("Unknown format: %s", format)
|
||||
}
|
||||
|
||||
return formatter, err
|
||||
}
|
||||
|
||||
func newPrettyFormatter(showMeta bool) Formatter {
|
||||
return &prettyFormatter{showMeta}
|
||||
}
|
||||
|
||||
type prettyFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatBindingRule(rule *api.ACLBindingRule) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("ID: %s\n", rule.ID))
|
||||
if rule.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", rule.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("AuthMethod: %s\n", rule.AuthMethod))
|
||||
buffer.WriteString(fmt.Sprintf("Description: %s\n", rule.Description))
|
||||
buffer.WriteString(fmt.Sprintf("BindType: %s\n", rule.BindType))
|
||||
buffer.WriteString(fmt.Sprintf("BindName: %s\n", rule.BindName))
|
||||
buffer.WriteString(fmt.Sprintf("Selector: %s\n", rule.Selector))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", rule.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", rule.ModifyIndex))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatBindingRuleList(rules []*api.ACLBindingRule) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, rule := range rules {
|
||||
buffer.WriteString(f.formatBindingRuleListEntry(rule))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) formatBindingRuleListEntry(rule *api.ACLBindingRule) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("%s:\n", rule.ID))
|
||||
if rule.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf(" Namespace: %s\n", rule.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" AuthMethod: %s\n", rule.AuthMethod))
|
||||
buffer.WriteString(fmt.Sprintf(" Description: %s\n", rule.Description))
|
||||
buffer.WriteString(fmt.Sprintf(" BindType: %s\n", rule.BindType))
|
||||
buffer.WriteString(fmt.Sprintf(" BindName: %s\n", rule.BindName))
|
||||
buffer.WriteString(fmt.Sprintf(" Selector: %s\n", rule.Selector))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf(" Create Index: %d\n", rule.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf(" Modify Index: %d\n", rule.ModifyIndex))
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func newJSONFormatter(showMeta bool) Formatter {
|
||||
return &jsonFormatter{showMeta}
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatBindingRule(rule *api.ACLBindingRule) (string, error) {
|
||||
b, err := json.MarshalIndent(rule, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal binding rule:, %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatBindingRuleList(rules []*api.ACLBindingRule) (string, error) {
|
||||
b, err := json.MarshalIndent(rules, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal binding rules:, %v", err)
|
||||
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
|
@ -3,8 +3,9 @@ package bindingrulelist
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/bindingrule"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -24,6 +25,7 @@ type cmd struct {
|
|||
authMethodName string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -44,6 +46,13 @@ func (c *cmd) init() {
|
|||
"Only show rules linked to the auth method with the given name.",
|
||||
)
|
||||
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
bindingrule.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(bindingrule.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -68,8 +77,19 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
acl.PrintBindingRuleListEntry(rule, c.UI, c.showMeta)
|
||||
formatter, err := bindingrule.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatBindingRuleList(rules)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package bindingrulelist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
// activate testing auth method
|
||||
|
@ -161,4 +163,29 @@ func TestBindingRuleListCommand(t *testing.T) {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("normal json formatted", func(t *testing.T) {
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
|
||||
for i, v := range ruleIDs {
|
||||
require.Contains(t, output, fmt.Sprintf("test-rule-%d", i))
|
||||
require.Contains(t, output, v)
|
||||
}
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@ package bindingruleread
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/bindingrule"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -24,6 +26,7 @@ type cmd struct {
|
|||
ruleID string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -46,6 +49,12 @@ func (c *cmd) init() {
|
|||
"matches multiple binding rule IDs",
|
||||
)
|
||||
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
bindingrule.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(bindingrule.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -84,7 +93,21 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
acl.PrintBindingRule(rule, c.UI, c.showMeta)
|
||||
formatter, err := bindingrule.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatBindingRule(rule)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -146,4 +146,26 @@ func TestBindingRuleReadCommand(t *testing.T) {
|
|||
require.Contains(t, output, fmt.Sprintf("test rule"))
|
||||
require.Contains(t, output, id)
|
||||
})
|
||||
|
||||
t.Run("read by id json formatted", func(t *testing.T) {
|
||||
id := createRule(t)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-id=" + id,
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, fmt.Sprintf("test rule"))
|
||||
require.Contains(t, output, id)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ package bindingruleupdate
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/bindingrule"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -31,6 +33,7 @@ type cmd struct {
|
|||
|
||||
noMerge bool
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -88,6 +91,13 @@ func (c *cmd) init() {
|
|||
"with the exception of the binding rule ID which is immutable.",
|
||||
)
|
||||
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
bindingrule.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(bindingrule.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -171,8 +181,21 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
c.UI.Info(fmt.Sprintf("Binding rule updated successfully"))
|
||||
acl.PrintBindingRule(rule, c.UI, c.showMeta)
|
||||
formatter, err := bindingrule.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatBindingRule(rule)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package bindingruleupdate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
// activate testing auth method
|
||||
|
@ -424,6 +426,45 @@ func TestBindingRuleUpdateCommand(t *testing.T) {
|
|||
require.Equal(t, "role-updated", rule.BindName)
|
||||
require.Empty(t, rule.Selector)
|
||||
})
|
||||
|
||||
t.Run("update all fields json formatted", func(t *testing.T) {
|
||||
id := createRule(t)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-id", id,
|
||||
"-description=test rule edited",
|
||||
"-bind-type", "role",
|
||||
"-bind-name=role-updated",
|
||||
"-selector=serviceaccount.namespace==alt and serviceaccount.name==demo",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String())
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
rule, _, err := client.ACL().BindingRuleRead(
|
||||
id,
|
||||
&api.QueryOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rule)
|
||||
|
||||
require.Equal(t, "test rule edited", rule.Description)
|
||||
require.Equal(t, "role-updated", rule.BindName)
|
||||
require.Equal(t, api.BindingRuleBindTypeRole, rule.BindType)
|
||||
require.Equal(t, "serviceaccount.namespace==alt and serviceaccount.name==demo", rule.Selector)
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBindingRuleUpdateCommand_noMerge(t *testing.T) {
|
||||
|
|
|
@ -3,8 +3,9 @@ package bootstrap
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/token"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -16,14 +17,21 @@ func New(ui cli.Ui) *cmd {
|
|||
}
|
||||
|
||||
type cmd struct {
|
||||
UI cli.Ui
|
||||
flags *flag.FlagSet
|
||||
http *flags.HTTPFlags
|
||||
help string
|
||||
UI cli.Ui
|
||||
flags *flag.FlagSet
|
||||
http *flags.HTTPFlags
|
||||
help string
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
token.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -41,13 +49,26 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
token, _, err := client.ACL().Bootstrap()
|
||||
t, _, err := client.ACL().Bootstrap()
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed ACL bootstrapping: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
acl.PrintToken(token, c.UI, false)
|
||||
formatter, err := token.NewFormatter(c.format, false)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatToken(t)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package bootstrap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBootstrapCommand_noTabs(t *testing.T) {
|
||||
|
@ -21,7 +23,7 @@ func TestBootstrapCommand_noTabs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBootstrapCommand(t *testing.T) {
|
||||
func TestBootstrapCommand_Pretty(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -51,3 +53,39 @@ func TestBootstrapCommand(t *testing.T) {
|
|||
assert.Contains(output, "Bootstrap Token")
|
||||
assert.Contains(output, structs.ACLPolicyGlobalManagementID)
|
||||
}
|
||||
|
||||
func TestBootstrapCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
assert.Contains(output, "Bootstrap Token")
|
||||
assert.Contains(output, structs.ACLPolicyGlobalManagementID)
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
require.NoError(t, err, "token unmarshalling error")
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/api"
|
||||
aclhelpers "github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/policy"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/hashicorp/consul/command/helpers"
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -33,6 +35,7 @@ type cmd struct {
|
|||
fromToken string
|
||||
tokenIsSecret bool
|
||||
showMeta bool
|
||||
format string
|
||||
|
||||
testStdin io.Reader
|
||||
}
|
||||
|
@ -53,6 +56,12 @@ func (c *cmd) init() {
|
|||
"Similar to the -rules option the token to use can be loaded from stdin or from a file")
|
||||
c.flags.BoolVar(&c.tokenIsSecret, "token-secret", false, "Indicates the token provided with "+
|
||||
"-from-token is a SecretID and not an AccessorID")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
policy.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(policy.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -114,13 +123,26 @@ func (c *cmd) Run(args []string) int {
|
|||
Rules: rules,
|
||||
}
|
||||
|
||||
policy, _, err := client.ACL().PolicyCreate(newPolicy, nil)
|
||||
p, _, err := client.ACL().PolicyCreate(newPolicy, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to create new policy: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
aclhelpers.PrintPolicy(policy, c.UI, c.showMeta)
|
||||
formatter, err := policy.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatPolicy(p)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package policycreate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -58,3 +60,46 @@ func TestPolicyCreateCommand(t *testing.T) {
|
|||
require.Equal(code, 0)
|
||||
require.Empty(ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
func TestPolicyCreateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
rules := []byte("service \"\" { policy = \"write\" }")
|
||||
err := ioutil.WriteFile(testDir+"/rules.hcl", rules, 0644)
|
||||
require.NoError(err)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=foobar",
|
||||
"-rules=@" + testDir + "/rules.hcl",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(code, 0)
|
||||
require.Empty(ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
PrettyFormat string = "pretty"
|
||||
JSONFormat string = "json"
|
||||
)
|
||||
|
||||
// Formatter defines methods provided by policy command output formatter
|
||||
type Formatter interface {
|
||||
FormatPolicy(policy *api.ACLPolicy) (string, error)
|
||||
FormatPolicyList(policies []*api.ACLPolicyListEntry) (string, error)
|
||||
}
|
||||
|
||||
// GetSupportedFormats returns supported formats
|
||||
func GetSupportedFormats() []string {
|
||||
return []string{PrettyFormat, JSONFormat}
|
||||
}
|
||||
|
||||
// NewFormatter returns Formatter implementation
|
||||
func NewFormatter(format string, showMeta bool) (formatter Formatter, err error) {
|
||||
switch format {
|
||||
case PrettyFormat:
|
||||
formatter = newPrettyFormatter(showMeta)
|
||||
case JSONFormat:
|
||||
formatter = newJSONFormatter(showMeta)
|
||||
default:
|
||||
err = fmt.Errorf("Unknown format: %s", format)
|
||||
}
|
||||
|
||||
return formatter, err
|
||||
}
|
||||
|
||||
func newPrettyFormatter(showMeta bool) Formatter {
|
||||
return &prettyFormatter{showMeta}
|
||||
}
|
||||
|
||||
type prettyFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatPolicy(policy *api.ACLPolicy) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("ID: %s\n", policy.ID))
|
||||
buffer.WriteString(fmt.Sprintf("Name: %s\n", policy.Name))
|
||||
if policy.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", policy.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("Description: %s\n", policy.Description))
|
||||
buffer.WriteString(fmt.Sprintf("Datacenters: %s\n", strings.Join(policy.Datacenters, ", ")))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf("Hash: %x\n", policy.Hash))
|
||||
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", policy.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", policy.ModifyIndex))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintln("Rules:"))
|
||||
buffer.WriteString(policy.Rules)
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatPolicyList(policies []*api.ACLPolicyListEntry) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, policy := range policies {
|
||||
buffer.WriteString(f.formatPolicyListEntry(policy))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) formatPolicyListEntry(policy *api.ACLPolicyListEntry) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("%s:\n", policy.Name))
|
||||
buffer.WriteString(fmt.Sprintf(" ID: %s\n", policy.ID))
|
||||
if policy.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf(" Namespace: %s\n", policy.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" Description: %s\n", policy.Description))
|
||||
buffer.WriteString(fmt.Sprintf(" Datacenters: %s\n", strings.Join(policy.Datacenters, ", ")))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf(" Hash: %x\n", policy.Hash))
|
||||
buffer.WriteString(fmt.Sprintf(" Create Index: %d\n", policy.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf(" Modify Index: %d\n", policy.ModifyIndex))
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func newJSONFormatter(showMeta bool) Formatter {
|
||||
return &jsonFormatter{showMeta}
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatPolicy(policy *api.ACLPolicy) (string, error) {
|
||||
b, err := json.MarshalIndent(policy, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal policy: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatPolicyList(policies []*api.ACLPolicyListEntry) (string, error) {
|
||||
b, err := json.MarshalIndent(policies, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal policies: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
|
@ -3,8 +3,9 @@ package policylist
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/policy"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -22,12 +23,19 @@ type cmd struct {
|
|||
help string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
c.flags.BoolVar(&c.showMeta, "meta", false, "Indicates that policy metadata such "+
|
||||
"as the content hash and raft indices should be shown for each entry")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
policy.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(policy.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -53,8 +61,18 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
for _, policy := range policies {
|
||||
acl.PrintPolicyListEntry(policy, c.UI, c.showMeta)
|
||||
formatter, err := policy.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatPolicyList(policies)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package policylist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -75,3 +76,62 @@ func TestPolicyListCommand(t *testing.T) {
|
|||
assert.Contains(output, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyListCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
var policyIDs []string
|
||||
|
||||
// Create a couple polices to list
|
||||
client := a.Client()
|
||||
for i := 0; i < 5; i++ {
|
||||
name := fmt.Sprintf("test-policy-%d", i)
|
||||
|
||||
policy, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: name},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
policyIDs = append(policyIDs, policy.ID)
|
||||
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
|
||||
for i, v := range policyIDs {
|
||||
assert.Contains(output, fmt.Sprintf("test-policy-%d", i))
|
||||
assert.Contains(output, v)
|
||||
}
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@ package policyread
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/policy"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -24,6 +26,7 @@ type cmd struct {
|
|||
policyID string
|
||||
policyName string
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -34,6 +37,12 @@ func (c *cmd) init() {
|
|||
"It may be specified as a unique ID prefix but will error if the prefix "+
|
||||
"matches multiple policy IDs")
|
||||
c.flags.StringVar(&c.policyName, "name", "", "The name of the policy to read.")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
policy.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(policy.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -68,12 +77,26 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
policy, _, err := client.ACL().PolicyRead(policyID, nil)
|
||||
p, _, err := client.ACL().PolicyRead(policyID, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading policy %q: %v", policyID, err))
|
||||
return 1
|
||||
}
|
||||
acl.PrintPolicy(policy, c.UI, c.showMeta)
|
||||
|
||||
formatter, err := policy.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatPolicy(p)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package policyread
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -67,3 +68,54 @@ func TestPolicyReadCommand(t *testing.T) {
|
|||
assert.Contains(output, fmt.Sprintf("test-policy"))
|
||||
assert.Contains(output, policy.ID)
|
||||
}
|
||||
|
||||
func TestPolicyReadCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
// Create a policy
|
||||
client := a.Client()
|
||||
|
||||
policy, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: "test-policy"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
assert.NoError(err)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-id=" + policy.ID,
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
assert.Contains(output, fmt.Sprintf("test-policy"))
|
||||
assert.Contains(output, policy.ID)
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
|
|
@ -4,9 +4,11 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/policy"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/hashicorp/consul/command/helpers"
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -34,6 +36,7 @@ type cmd struct {
|
|||
rules string
|
||||
noMerge bool
|
||||
showMeta bool
|
||||
format string
|
||||
testStdin io.Reader
|
||||
}
|
||||
|
||||
|
@ -54,6 +57,13 @@ func (c *cmd) init() {
|
|||
c.flags.BoolVar(&c.noMerge, "no-merge", false, "Do not merge the current policy "+
|
||||
"information with what is provided to the command. Instead overwrite all fields "+
|
||||
"with the exception of the policy ID which is immutable.")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
policy.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(policy.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -116,7 +126,7 @@ func (c *cmd) Run(args []string) int {
|
|||
Rules: rules,
|
||||
}
|
||||
} else {
|
||||
policy, _, err := client.ACL().PolicyRead(policyID, nil)
|
||||
p, _, err := client.ACL().PolicyRead(policyID, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading policy %q: %v", policyID, err))
|
||||
return 1
|
||||
|
@ -124,10 +134,10 @@ func (c *cmd) Run(args []string) int {
|
|||
|
||||
updated = &api.ACLPolicy{
|
||||
ID: policyID,
|
||||
Name: policy.Name,
|
||||
Description: policy.Description,
|
||||
Datacenters: policy.Datacenters,
|
||||
Rules: policy.Rules,
|
||||
Name: p.Name,
|
||||
Description: p.Description,
|
||||
Datacenters: p.Datacenters,
|
||||
Rules: p.Rules,
|
||||
}
|
||||
|
||||
if c.nameSet {
|
||||
|
@ -144,14 +154,26 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
}
|
||||
|
||||
policy, _, err := client.ACL().PolicyUpdate(updated, nil)
|
||||
p, _, err := client.ACL().PolicyUpdate(updated, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error updating policy %q: %v", policyID, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.UI.Info(fmt.Sprintf("Policy updated successfully"))
|
||||
acl.PrintPolicy(policy, c.UI, c.showMeta)
|
||||
formatter, err := policy.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatPolicy(p)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package policyupdate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -69,3 +70,56 @@ func TestPolicyUpdateCommand(t *testing.T) {
|
|||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
func TestPolicyUpdateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
rules := []byte("service \"\" { policy = \"write\" }")
|
||||
err := ioutil.WriteFile(testDir+"/rules.hcl", rules, 0644)
|
||||
assert.NoError(err)
|
||||
|
||||
// Create a policy
|
||||
client := a.Client()
|
||||
|
||||
policy, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: "test-policy"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
assert.NoError(err)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-id=" + policy.ID,
|
||||
"-name=new-name",
|
||||
"-rules=@" + testDir + "/rules.hcl",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ package rolecreate
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
aclhelpers "github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/role"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -30,6 +31,7 @@ type cmd struct {
|
|||
serviceIdents []string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -45,6 +47,12 @@ func (c *cmd) init() {
|
|||
c.flags.Var((*flags.AppendSliceValue)(&c.serviceIdents), "service-identity", "Name of a "+
|
||||
"service identity to use for this role. May be specified multiple times. Format is "+
|
||||
"the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
role.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(role.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -101,13 +109,26 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
newRole.ServiceIdentities = parsedServiceIdents
|
||||
|
||||
role, _, err := client.ACL().RoleCreate(newRole, nil)
|
||||
r, _, err := client.ACL().RoleCreate(newRole, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to create new role: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
aclhelpers.PrintRole(role, c.UI, c.showMeta)
|
||||
formatter, err := role.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatRole(r)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package rolecreate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -21,7 +23,7 @@ func TestRoleCreateCommand_noTabs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRoleCreateCommand(t *testing.T) {
|
||||
func TestRoleCreateCommand_Pretty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
|
@ -111,3 +113,54 @@ func TestRoleCreateCommand(t *testing.T) {
|
|||
require.Empty(t, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleCreateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
// Create a policy
|
||||
client := a.Client()
|
||||
|
||||
policy, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: "test-policy"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create with policy by name
|
||||
{
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-name=role-with-policy-by-name",
|
||||
"-description=test-role",
|
||||
"-policy-name=" + policy.Name,
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
package role
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
PrettyFormat string = "pretty"
|
||||
JSONFormat string = "json"
|
||||
)
|
||||
|
||||
// Formatter defines methods provided by role command output formatter
|
||||
type Formatter interface {
|
||||
FormatRole(role *api.ACLRole) (string, error)
|
||||
FormatRoleList(roles []*api.ACLRole) (string, error)
|
||||
}
|
||||
|
||||
// GetSupportedFormats returns supported formats
|
||||
func GetSupportedFormats() []string {
|
||||
return []string{PrettyFormat, JSONFormat}
|
||||
}
|
||||
|
||||
// NewFormatter returns Formatter implementation
|
||||
func NewFormatter(format string, showMeta bool) (formatter Formatter, err error) {
|
||||
switch format {
|
||||
case PrettyFormat:
|
||||
formatter = newPrettyFormatter(showMeta)
|
||||
case JSONFormat:
|
||||
formatter = newJSONFormatter(showMeta)
|
||||
default:
|
||||
err = fmt.Errorf("Unknown format: %s", format)
|
||||
}
|
||||
|
||||
return formatter, err
|
||||
}
|
||||
|
||||
func newPrettyFormatter(showMeta bool) Formatter {
|
||||
return &prettyFormatter{showMeta}
|
||||
}
|
||||
|
||||
type prettyFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatRole(role *api.ACLRole) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("ID: %s\n", role.ID))
|
||||
buffer.WriteString(fmt.Sprintf("Name: %s\n", role.Name))
|
||||
if role.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", role.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("Description: %s\n", role.Description))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf("Hash: %x\n", role.Hash))
|
||||
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", role.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", role.ModifyIndex))
|
||||
}
|
||||
if len(role.Policies) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Policies:"))
|
||||
for _, policy := range role.Policies {
|
||||
buffer.WriteString(fmt.Sprintf(" %s - %s\n", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(role.ServiceIdentities) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Service Identities:"))
|
||||
for _, svcid := range role.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: %s)\n", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: all)\n", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatRoleList(roles []*api.ACLRole) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, role := range roles {
|
||||
buffer.WriteString(f.formatRoleListEntry(role))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) formatRoleListEntry(role *api.ACLRole) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("%s:\n", role.Name))
|
||||
buffer.WriteString(fmt.Sprintf(" ID: %s\n", role.ID))
|
||||
if role.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf(" Namespace: %s\n", role.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" Description: %s\n", role.Description))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf(" Hash: %x\n", role.Hash))
|
||||
buffer.WriteString(fmt.Sprintf(" Create Index: %d\n", role.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf(" Modify Index: %d\n", role.ModifyIndex))
|
||||
}
|
||||
if len(role.Policies) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln(" Policies:"))
|
||||
for _, policy := range role.Policies {
|
||||
buffer.WriteString(fmt.Sprintf(" %s - %s\n", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(role.ServiceIdentities) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln(" Service Identities:"))
|
||||
for _, svcid := range role.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: %s)\n", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: all)\n", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func newJSONFormatter(showMeta bool) Formatter {
|
||||
return &jsonFormatter{showMeta}
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatRole(role *api.ACLRole) (string, error) {
|
||||
b, err := json.MarshalIndent(role, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal role: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatRoleList(roles []*api.ACLRole) (string, error) {
|
||||
b, err := json.MarshalIndent(roles, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal roles: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
|
@ -3,8 +3,9 @@ package rolelist
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/role"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -22,12 +23,19 @@ type cmd struct {
|
|||
help string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
c.flags.BoolVar(&c.showMeta, "meta", false, "Indicates that policy metadata such "+
|
||||
"as the content hash and raft indices should be shown for each entry")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
role.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(role.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -53,8 +61,18 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
acl.PrintRoleListEntry(role, c.UI, c.showMeta)
|
||||
formatter, err := role.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatRoleList(roles)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package rolelist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -78,3 +80,65 @@ func TestRoleListCommand(t *testing.T) {
|
|||
require.Contains(output, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleListCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
var roleIDs []string
|
||||
|
||||
// Create a couple roles to list
|
||||
client := a.Client()
|
||||
svcids := []*api.ACLServiceIdentity{
|
||||
&api.ACLServiceIdentity{ServiceName: "fake"},
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
name := fmt.Sprintf("test-role-%d", i)
|
||||
|
||||
role, _, err := client.ACL().RoleCreate(
|
||||
&api.ACLRole{Name: name, ServiceIdentities: svcids},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
roleIDs = append(roleIDs, role.ID)
|
||||
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(code, 0)
|
||||
require.Empty(ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
|
||||
for i, v := range roleIDs {
|
||||
require.Contains(output, fmt.Sprintf("test-role-%d", i))
|
||||
require.Contains(output, v)
|
||||
}
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ package roleread
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/role"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -25,6 +27,7 @@ type cmd struct {
|
|||
roleID string
|
||||
roleName string
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -35,6 +38,12 @@ func (c *cmd) init() {
|
|||
"It may be specified as a unique ID prefix but will error if the prefix "+
|
||||
"matches multiple policy IDs")
|
||||
c.flags.StringVar(&c.roleName, "name", "", "The name of the role to read.")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
role.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(role.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -58,7 +67,7 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
var role *api.ACLRole
|
||||
var r *api.ACLRole
|
||||
|
||||
if c.roleID != "" {
|
||||
roleID, err := acl.GetRoleIDFromPartial(client, c.roleID)
|
||||
|
@ -66,27 +75,40 @@ func (c *cmd) Run(args []string) int {
|
|||
c.UI.Error(fmt.Sprintf("Error determining role ID: %v", err))
|
||||
return 1
|
||||
}
|
||||
role, _, err = client.ACL().RoleRead(roleID, nil)
|
||||
r, _, err = client.ACL().RoleRead(roleID, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading role %q: %v", roleID, err))
|
||||
return 1
|
||||
} else if role == nil {
|
||||
} else if r == nil {
|
||||
c.UI.Error(fmt.Sprintf("Role not found with ID %q", roleID))
|
||||
return 1
|
||||
}
|
||||
|
||||
} else {
|
||||
role, _, err = client.ACL().RoleReadByName(c.roleName, nil)
|
||||
r, _, err = client.ACL().RoleReadByName(c.roleName, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading role %q: %v", c.roleName, err))
|
||||
return 1
|
||||
} else if role == nil {
|
||||
} else if r == nil {
|
||||
c.UI.Error(fmt.Sprintf("Role not found with name %q", c.roleName))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
acl.PrintRole(role, c.UI, c.showMeta)
|
||||
formatter, err := role.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatRole(r)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package roleread
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -189,3 +191,62 @@ func TestRoleReadCommand(t *testing.T) {
|
|||
require.Contains(t, output, role.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleReadCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
client := a.Client()
|
||||
|
||||
t.Run("read by id", func(t *testing.T) {
|
||||
// create a role
|
||||
role, _, err := client.ACL().RoleCreate(
|
||||
&api.ACLRole{
|
||||
Name: "test-role-by-id",
|
||||
ServiceIdentities: []*api.ACLServiceIdentity{
|
||||
&api.ACLServiceIdentity{
|
||||
ServiceName: "fake",
|
||||
},
|
||||
},
|
||||
},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-id=" + role.ID,
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0)
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
require.Contains(t, output, fmt.Sprintf("test-role"))
|
||||
require.Contains(t, output, role.ID)
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ package roleupdate
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/role"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -31,6 +33,7 @@ type cmd struct {
|
|||
|
||||
noMerge bool
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -52,6 +55,12 @@ func (c *cmd) init() {
|
|||
c.flags.BoolVar(&c.noMerge, "no-merge", false, "Do not merge the current role "+
|
||||
"information with what is provided to the command. Instead overwrite all fields "+
|
||||
"with the exception of the role ID which is immutable.")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
role.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(role.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -98,9 +107,9 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
var role *api.ACLRole
|
||||
var r *api.ACLRole
|
||||
if c.noMerge {
|
||||
role = &api.ACLRole{
|
||||
r = &api.ACLRole{
|
||||
ID: c.roleID,
|
||||
Name: c.name,
|
||||
Description: c.description,
|
||||
|
@ -110,7 +119,7 @@ func (c *cmd) Run(args []string) int {
|
|||
for _, policyName := range c.policyNames {
|
||||
// We could resolve names to IDs here but there isn't any reason
|
||||
// why its would be better than allowing the agent to do it.
|
||||
role.Policies = append(role.Policies, &api.ACLRolePolicyLink{Name: policyName})
|
||||
r.Policies = append(r.Policies, &api.ACLRolePolicyLink{Name: policyName})
|
||||
}
|
||||
|
||||
for _, policyID := range c.policyIDs {
|
||||
|
@ -119,21 +128,21 @@ func (c *cmd) Run(args []string) int {
|
|||
c.UI.Error(fmt.Sprintf("Error resolving policy ID %s: %v", policyID, err))
|
||||
return 1
|
||||
}
|
||||
role.Policies = append(role.Policies, &api.ACLRolePolicyLink{ID: policyID})
|
||||
r.Policies = append(r.Policies, &api.ACLRolePolicyLink{ID: policyID})
|
||||
}
|
||||
} else {
|
||||
role = currentRole
|
||||
r = currentRole
|
||||
|
||||
if c.name != "" {
|
||||
role.Name = c.name
|
||||
r.Name = c.name
|
||||
}
|
||||
if c.description != "" {
|
||||
role.Description = c.description
|
||||
r.Description = c.description
|
||||
}
|
||||
|
||||
for _, policyName := range c.policyNames {
|
||||
found := false
|
||||
for _, link := range role.Policies {
|
||||
for _, link := range r.Policies {
|
||||
if link.Name == policyName {
|
||||
found = true
|
||||
break
|
||||
|
@ -144,7 +153,7 @@ func (c *cmd) Run(args []string) int {
|
|||
// We could resolve names to IDs here but there isn't any
|
||||
// reason why its would be better than allowing the agent to do
|
||||
// it.
|
||||
role.Policies = append(role.Policies, &api.ACLRolePolicyLink{Name: policyName})
|
||||
r.Policies = append(r.Policies, &api.ACLRolePolicyLink{Name: policyName})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +165,7 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
found := false
|
||||
|
||||
for _, link := range role.Policies {
|
||||
for _, link := range r.Policies {
|
||||
if link.ID == policyID {
|
||||
found = true
|
||||
break
|
||||
|
@ -164,13 +173,13 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
|
||||
if !found {
|
||||
role.Policies = append(role.Policies, &api.ACLRolePolicyLink{ID: policyID})
|
||||
r.Policies = append(r.Policies, &api.ACLRolePolicyLink{ID: policyID})
|
||||
}
|
||||
}
|
||||
|
||||
for _, svcid := range parsedServiceIdents {
|
||||
found := -1
|
||||
for i, link := range role.ServiceIdentities {
|
||||
for i, link := range r.ServiceIdentities {
|
||||
if link.ServiceName == svcid.ServiceName {
|
||||
found = i
|
||||
break
|
||||
|
@ -178,21 +187,33 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
|
||||
if found != -1 {
|
||||
role.ServiceIdentities[found] = svcid
|
||||
r.ServiceIdentities[found] = svcid
|
||||
} else {
|
||||
role.ServiceIdentities = append(role.ServiceIdentities, svcid)
|
||||
r.ServiceIdentities = append(r.ServiceIdentities, svcid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
role, _, err = client.ACL().RoleUpdate(role, nil)
|
||||
r, _, err = client.ACL().RoleUpdate(r, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error updating role %q: %v", roleID, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.UI.Info(fmt.Sprintf("Role updated successfully"))
|
||||
acl.PrintRole(role, c.UI, c.showMeta)
|
||||
formatter, err := role.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatRole(r)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package roleupdate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
|
@ -192,6 +194,88 @@ func TestRoleUpdateCommand(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestRoleUpdateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
client := a.Client()
|
||||
|
||||
// Create policy
|
||||
policy1, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: "test-policy1"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
role, _, err := client.ACL().RoleCreate(
|
||||
&api.ACLRole{
|
||||
Name: "test-role",
|
||||
ServiceIdentities: []*api.ACLServiceIdentity{
|
||||
&api.ACLServiceIdentity{
|
||||
ServiceName: "fake",
|
||||
},
|
||||
},
|
||||
},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("update a role that does not exist", func(t *testing.T) {
|
||||
fakeID, err := uuid.GenerateUUID()
|
||||
require.NoError(t, err)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-id=" + fakeID,
|
||||
"-token=root",
|
||||
"-policy-name=" + policy1.Name,
|
||||
"-description=test role edited",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 1)
|
||||
require.Contains(t, ui.ErrorWriter.String(), "Role not found with ID")
|
||||
})
|
||||
|
||||
t.Run("update with policy by name", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-id=" + role.ID,
|
||||
"-token=root",
|
||||
"-policy-name=" + policy1.Name,
|
||||
"-description=test role edited",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String())
|
||||
require.Empty(t, ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleUpdateCommand_noMerge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ package tokenclone
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/token"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -23,6 +25,7 @@ type cmd struct {
|
|||
|
||||
tokenID string
|
||||
description string
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -32,6 +35,12 @@ func (c *cmd) init() {
|
|||
"matches multiple token Accessor IDs. The special value of 'anonymous' may "+
|
||||
"be provided instead of the anonymous tokens accessor ID")
|
||||
c.flags.StringVar(&c.description, "description", "", "A description of the new cloned token")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
token.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -61,14 +70,26 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
token, _, err := client.ACL().TokenClone(tokenID, c.description, nil)
|
||||
t, _, err := client.ACL().TokenClone(tokenID, c.description, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error cloning token: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.UI.Info("Token cloned successfully.")
|
||||
acl.PrintToken(token, c.UI, false)
|
||||
formatter, err := token.NewFormatter(c.format, false)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatToken(t)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tokenclone
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
@ -12,13 +13,13 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func parseCloneOutput(t *testing.T, output string) *api.ACLToken {
|
||||
// This will only work for non-legacy tokens
|
||||
re := regexp.MustCompile("Token cloned successfully.\n" +
|
||||
"AccessorID: ([a-zA-Z0-9\\-]{36})\n" +
|
||||
re := regexp.MustCompile("AccessorID: ([a-zA-Z0-9\\-]{36})\n" +
|
||||
"SecretID: ([a-zA-Z0-9\\-]{36})\n" +
|
||||
"(?:Namespace: default\n)?" +
|
||||
"Description: ([^\n]*)\n" +
|
||||
|
@ -58,7 +59,7 @@ func TestTokenCloneCommand_noTabs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenCloneCommand(t *testing.T) {
|
||||
func TestTokenCloneCommand_Pretty(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := require.New(t)
|
||||
|
||||
|
@ -164,3 +165,84 @@ func TestTokenCloneCommand(t *testing.T) {
|
|||
req.Equal(cloned.Policies, apiToken.Policies)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTokenCloneCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := require.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
// Create a policy
|
||||
client := a.Client()
|
||||
|
||||
_, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: "test-policy"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
req.NoError(err)
|
||||
|
||||
// create a token
|
||||
token, _, err := client.ACL().TokenCreate(
|
||||
&api.ACLToken{Description: "test", Policies: []*api.ACLTokenPolicyLink{&api.ACLTokenPolicyLink{Name: "test-policy"}}},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
req.NoError(err)
|
||||
|
||||
// clone with description
|
||||
t.Run("Description", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-id=" + token.AccessorID,
|
||||
"-token=root",
|
||||
"-description=test cloned",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
req.Empty(ui.ErrorWriter.String())
|
||||
req.Equal(code, 0)
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// clone without description
|
||||
t.Run("Without Description", func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-id=" + token.AccessorID,
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
req.Empty(ui.ErrorWriter.String())
|
||||
req.Equal(code, 0)
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(output), &jsonOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ package tokencreate
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/token"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -34,6 +36,7 @@ type cmd struct {
|
|||
expirationTTL time.Duration
|
||||
local bool
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -59,6 +62,12 @@ func (c *cmd) init() {
|
|||
"the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...")
|
||||
c.flags.DurationVar(&c.expirationTTL, "expires-ttl", 0, "Duration of time this "+
|
||||
"token should be valid for")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
token.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -131,13 +140,26 @@ func (c *cmd) Run(args []string) int {
|
|||
newToken.Roles = append(newToken.Roles, &api.ACLTokenRoleLink{ID: roleID})
|
||||
}
|
||||
|
||||
token, _, err := client.ACL().TokenCreate(newToken, nil)
|
||||
t, _, err := client.ACL().TokenCreate(newToken, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to create new token: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
acl.PrintToken(token, c.UI, c.showMeta)
|
||||
formatter, err := token.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatToken(t)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tokencreate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -21,7 +22,7 @@ func TestTokenCreateCommand_noTabs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenCreateCommand(t *testing.T) {
|
||||
func TestTokenCreateCommand_Pretty(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
|
@ -111,3 +112,54 @@ func TestTokenCreateCommand(t *testing.T) {
|
|||
require.Equal("3a69a8d8-c4d4-485d-9b19-b5b61648ea0c", token.SecretID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenCreateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
// Create a policy
|
||||
client := a.Client()
|
||||
|
||||
policy, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: "test-policy"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
require.NoError(err)
|
||||
|
||||
// create with policy by name
|
||||
{
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-policy-name=" + policy.Name,
|
||||
"-description=test token",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
require.Equal(code, 0)
|
||||
require.Empty(ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
require.NoError(err, "token unmarshalling error")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
package token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
const (
|
||||
PrettyFormat string = "pretty"
|
||||
JSONFormat string = "json"
|
||||
)
|
||||
|
||||
// Formatter defines methods provided by token command output formatter
|
||||
type Formatter interface {
|
||||
FormatToken(token *api.ACLToken) (string, error)
|
||||
FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error)
|
||||
}
|
||||
|
||||
// GetSupportedFormats returns supported formats
|
||||
func GetSupportedFormats() []string {
|
||||
return []string{PrettyFormat, JSONFormat}
|
||||
}
|
||||
|
||||
// NewFormatter returns Formatter implementation
|
||||
func NewFormatter(format string, showMeta bool) (formatter Formatter, err error) {
|
||||
switch format {
|
||||
case PrettyFormat:
|
||||
formatter = newPrettyFormatter(showMeta)
|
||||
case JSONFormat:
|
||||
formatter = newJSONFormatter(showMeta)
|
||||
default:
|
||||
err = fmt.Errorf("Unknown format: %s", format)
|
||||
}
|
||||
|
||||
return formatter, err
|
||||
}
|
||||
|
||||
func newPrettyFormatter(showMeta bool) Formatter {
|
||||
return &prettyFormatter{showMeta}
|
||||
}
|
||||
|
||||
type prettyFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatToken(token *api.ACLToken) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID))
|
||||
buffer.WriteString(fmt.Sprintf("SecretID: %s\n", token.SecretID))
|
||||
if token.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("Description: %s\n", token.Description))
|
||||
buffer.WriteString(fmt.Sprintf("Local: %t\n", token.Local))
|
||||
buffer.WriteString(fmt.Sprintf("Create Time: %v\n", token.CreateTime))
|
||||
if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() {
|
||||
buffer.WriteString(fmt.Sprintf("Expiration Time: %v\n", *token.ExpirationTime))
|
||||
}
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf("Hash: %x\n", token.Hash))
|
||||
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", token.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", token.ModifyIndex))
|
||||
}
|
||||
if len(token.Policies) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Policies:"))
|
||||
for _, policy := range token.Policies {
|
||||
buffer.WriteString(fmt.Sprintf(" %s - %s\n", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(token.Roles) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Roles:"))
|
||||
for _, role := range token.Roles {
|
||||
buffer.WriteString(fmt.Sprintf(" %s - %s\n", role.ID, role.Name))
|
||||
}
|
||||
}
|
||||
if len(token.ServiceIdentities) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Service Identities:"))
|
||||
for _, svcid := range token.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: %s)\n", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: all)\n", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
if token.Rules != "" {
|
||||
buffer.WriteString(fmt.Sprintln("Rules:"))
|
||||
buffer.WriteString(fmt.Sprintln(token.Rules))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
first := true
|
||||
for _, token := range tokens {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
buffer.WriteString(f.formatTokenListEntry(token))
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (f *prettyFormatter) formatTokenListEntry(token *api.ACLTokenListEntry) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("AccessorID: %s\n", token.AccessorID))
|
||||
if token.Namespace != "" {
|
||||
buffer.WriteString(fmt.Sprintf("Namespace: %s\n", token.Namespace))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("Description: %s\n", token.Description))
|
||||
buffer.WriteString(fmt.Sprintf("Local: %t\n", token.Local))
|
||||
buffer.WriteString(fmt.Sprintf("Create Time: %v\n", token.CreateTime))
|
||||
if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() {
|
||||
buffer.WriteString(fmt.Sprintf("Expiration Time: %v\n", *token.ExpirationTime))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("Legacy: %t\n", token.Legacy))
|
||||
if f.showMeta {
|
||||
buffer.WriteString(fmt.Sprintf("Hash: %x\n", token.Hash))
|
||||
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", token.CreateIndex))
|
||||
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", token.ModifyIndex))
|
||||
}
|
||||
if len(token.Policies) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Policies:"))
|
||||
for _, policy := range token.Policies {
|
||||
buffer.WriteString(fmt.Sprintf(" %s - %s\n", policy.ID, policy.Name))
|
||||
}
|
||||
}
|
||||
if len(token.Roles) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Roles:"))
|
||||
for _, role := range token.Roles {
|
||||
buffer.WriteString(fmt.Sprintf(" %s - %s\n", role.ID, role.Name))
|
||||
}
|
||||
}
|
||||
if len(token.ServiceIdentities) > 0 {
|
||||
buffer.WriteString(fmt.Sprintln("Service Identities:"))
|
||||
for _, svcid := range token.ServiceIdentities {
|
||||
if len(svcid.Datacenters) > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: %s)\n", svcid.ServiceName, strings.Join(svcid.Datacenters, ", ")))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenters: all)\n", svcid.ServiceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func newJSONFormatter(showMeta bool) Formatter {
|
||||
return &jsonFormatter{showMeta}
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
showMeta bool
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) {
|
||||
b, err := json.MarshalIndent(tokens, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal tokens: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (f *jsonFormatter) FormatToken(token *api.ACLToken) (string, error) {
|
||||
b, err := json.MarshalIndent(token, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to marshal token: %v", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
|
@ -3,8 +3,9 @@ package tokenlist
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/token"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -22,12 +23,19 @@ type cmd struct {
|
|||
help string
|
||||
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
c.flags.BoolVar(&c.showMeta, "meta", false, "Indicates that token metadata such "+
|
||||
"as the content hash and Raft indices should be shown for each entry")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
token.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -52,14 +60,18 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
first := true
|
||||
for _, token := range tokens {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
c.UI.Info("")
|
||||
}
|
||||
acl.PrintTokenListEntry(token, c.UI, c.showMeta)
|
||||
formatter, err := token.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatTokenList(tokens)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tokenlist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTokenListCommand_noTabs(t *testing.T) {
|
||||
|
@ -22,7 +24,7 @@ func TestTokenListCommand_noTabs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenListCommand(t *testing.T) {
|
||||
func TestTokenListCommand_Pretty(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -75,3 +77,56 @@ func TestTokenListCommand(t *testing.T) {
|
|||
assert.Contains(output, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenListCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
var tokenIds []string
|
||||
|
||||
// Create a couple tokens to list
|
||||
client := a.Client()
|
||||
for i := 0; i < 5; i++ {
|
||||
description := fmt.Sprintf("test token %d", i)
|
||||
|
||||
token, _, err := client.ACL().TokenCreate(
|
||||
&api.ACLToken{Description: description},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
tokenIds = append(tokenIds, token.AccessorID)
|
||||
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
require.NoError(t, err, "token unmarshalling error")
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ package tokenread
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/token"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -25,6 +27,7 @@ type cmd struct {
|
|||
tokenID string
|
||||
self bool
|
||||
showMeta bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -36,6 +39,12 @@ func (c *cmd) init() {
|
|||
c.flags.StringVar(&c.tokenID, "id", "", "The Accessor ID of the token to read. "+
|
||||
"It may be specified as a unique ID prefix but will error if the prefix "+
|
||||
"matches multiple token Accessor IDs")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
token.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
||||
)
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
flags.Merge(c.flags, c.http.ServerFlags())
|
||||
|
@ -59,7 +68,7 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
var token *api.ACLToken
|
||||
var t *api.ACLToken
|
||||
if !c.self {
|
||||
tokenID, err := acl.GetTokenIDFromPartial(client, c.tokenID)
|
||||
if err != nil {
|
||||
|
@ -67,20 +76,33 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
token, _, err = client.ACL().TokenRead(tokenID, nil)
|
||||
t, _, err = client.ACL().TokenRead(tokenID, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading token %q: %v", tokenID, err))
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
token, _, err = client.ACL().TokenReadSelf(nil)
|
||||
t, _, err = client.ACL().TokenReadSelf(nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reading token: %v", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
acl.PrintToken(token, c.UI, c.showMeta)
|
||||
formatter, err := token.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
out, err := formatter.FormatToken(t)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tokenread
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTokenReadCommand_noTabs(t *testing.T) {
|
||||
|
@ -22,7 +24,7 @@ func TestTokenReadCommand_noTabs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenReadCommand(t *testing.T) {
|
||||
func TestTokenReadCommand_Pretty(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -68,3 +70,50 @@ func TestTokenReadCommand(t *testing.T) {
|
|||
assert.Contains(output, token.AccessorID)
|
||||
assert.Contains(output, token.SecretID)
|
||||
}
|
||||
|
||||
func TestTokenReadCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := New(ui)
|
||||
|
||||
// Create a token
|
||||
client := a.Client()
|
||||
|
||||
token, _, err := client.ACL().TokenCreate(
|
||||
&api.ACLToken{Description: "test"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
assert.NoError(err)
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-token=root",
|
||||
"-id=" + token.AccessorID,
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err = json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
require.NoError(t, err, "token unmarshalling error")
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ package tokenupdate
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/command/acl"
|
||||
"github.com/hashicorp/consul/command/acl/token"
|
||||
"github.com/hashicorp/consul/command/flags"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -34,6 +36,7 @@ type cmd struct {
|
|||
mergeServiceIdents bool
|
||||
showMeta bool
|
||||
upgradeLegacy bool
|
||||
format string
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -66,6 +69,12 @@ func (c *cmd) init() {
|
|||
"token to behave exactly like a new token but keep the same Secret.\n"+
|
||||
"WARNING: you must ensure that the new policy or policies specified grant "+
|
||||
"equivalent or appropriate access for the existing clients using this token.")
|
||||
c.flags.StringVar(
|
||||
&c.format,
|
||||
"format",
|
||||
token.PrettyFormat,
|
||||
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
||||
)
|
||||
|
||||
c.http = &flags.HTTPFlags{}
|
||||
flags.Merge(c.flags, c.http.ClientFlags())
|
||||
|
@ -96,14 +105,14 @@ func (c *cmd) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
token, _, err := client.ACL().TokenRead(tokenID, nil)
|
||||
t, _, err := client.ACL().TokenRead(tokenID, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error when retrieving current token: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
if c.upgradeLegacy {
|
||||
if token.Rules == "" {
|
||||
if t.Rules == "" {
|
||||
// This is just for convenience it should actually be harmless to allow it
|
||||
// to go through anyway.
|
||||
c.UI.Error(fmt.Sprintf("Can't use -upgrade-legacy on a non-legacy token"))
|
||||
|
@ -111,7 +120,7 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
// Reset the rules to nothing forcing this to be updated as a non-legacy
|
||||
// token but with same secret.
|
||||
token.Rules = ""
|
||||
t.Rules = ""
|
||||
}
|
||||
|
||||
if c.description != "" {
|
||||
|
@ -121,7 +130,7 @@ func (c *cmd) Run(args []string) int {
|
|||
// manually giving the new description. If it's a real issue we can always
|
||||
// add another explicit `-remove-description` flag but it feels like an edge
|
||||
// case that's not going to be critical to anyone.
|
||||
token.Description = c.description
|
||||
t.Description = c.description
|
||||
}
|
||||
|
||||
parsedServiceIdents, err := acl.ExtractServiceIdentities(c.serviceIdents)
|
||||
|
@ -133,7 +142,7 @@ func (c *cmd) Run(args []string) int {
|
|||
if c.mergePolicies {
|
||||
for _, policyName := range c.policyNames {
|
||||
found := false
|
||||
for _, link := range token.Policies {
|
||||
for _, link := range t.Policies {
|
||||
if link.Name == policyName {
|
||||
found = true
|
||||
break
|
||||
|
@ -143,7 +152,7 @@ func (c *cmd) Run(args []string) int {
|
|||
if !found {
|
||||
// We could resolve names to IDs here but there isn't any reason why its would be better
|
||||
// than allowing the agent to do it.
|
||||
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{Name: policyName})
|
||||
t.Policies = append(t.Policies, &api.ACLTokenPolicyLink{Name: policyName})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +164,7 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
found := false
|
||||
|
||||
for _, link := range token.Policies {
|
||||
for _, link := range t.Policies {
|
||||
if link.ID == policyID {
|
||||
found = true
|
||||
break
|
||||
|
@ -163,16 +172,16 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
|
||||
if !found {
|
||||
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{ID: policyID})
|
||||
t.Policies = append(t.Policies, &api.ACLTokenPolicyLink{ID: policyID})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
token.Policies = nil
|
||||
t.Policies = nil
|
||||
|
||||
for _, policyName := range c.policyNames {
|
||||
// We could resolve names to IDs here but there isn't any reason why its would be better
|
||||
// than allowing the agent to do it.
|
||||
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{Name: policyName})
|
||||
t.Policies = append(t.Policies, &api.ACLTokenPolicyLink{Name: policyName})
|
||||
}
|
||||
|
||||
for _, policyID := range c.policyIDs {
|
||||
|
@ -181,14 +190,14 @@ func (c *cmd) Run(args []string) int {
|
|||
c.UI.Error(fmt.Sprintf("Error resolving policy ID %s: %v", policyID, err))
|
||||
return 1
|
||||
}
|
||||
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{ID: policyID})
|
||||
t.Policies = append(t.Policies, &api.ACLTokenPolicyLink{ID: policyID})
|
||||
}
|
||||
}
|
||||
|
||||
if c.mergeRoles {
|
||||
for _, roleName := range c.roleNames {
|
||||
found := false
|
||||
for _, link := range token.Roles {
|
||||
for _, link := range t.Roles {
|
||||
if link.Name == roleName {
|
||||
found = true
|
||||
break
|
||||
|
@ -198,7 +207,7 @@ func (c *cmd) Run(args []string) int {
|
|||
if !found {
|
||||
// We could resolve names to IDs here but there isn't any reason why its would be better
|
||||
// than allowing the agent to do it.
|
||||
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{Name: roleName})
|
||||
t.Roles = append(t.Roles, &api.ACLTokenRoleLink{Name: roleName})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +219,7 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
found := false
|
||||
|
||||
for _, link := range token.Roles {
|
||||
for _, link := range t.Roles {
|
||||
if link.ID == roleID {
|
||||
found = true
|
||||
break
|
||||
|
@ -218,16 +227,16 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
|
||||
if !found {
|
||||
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{Name: roleID})
|
||||
t.Roles = append(t.Roles, &api.ACLTokenRoleLink{Name: roleID})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
token.Roles = nil
|
||||
t.Roles = nil
|
||||
|
||||
for _, roleName := range c.roleNames {
|
||||
// We could resolve names to IDs here but there isn't any reason why its would be better
|
||||
// than allowing the agent to do it.
|
||||
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{Name: roleName})
|
||||
t.Roles = append(t.Roles, &api.ACLTokenRoleLink{Name: roleName})
|
||||
}
|
||||
|
||||
for _, roleID := range c.roleIDs {
|
||||
|
@ -236,14 +245,14 @@ func (c *cmd) Run(args []string) int {
|
|||
c.UI.Error(fmt.Sprintf("Error resolving role ID %s: %v", roleID, err))
|
||||
return 1
|
||||
}
|
||||
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{ID: roleID})
|
||||
t.Roles = append(t.Roles, &api.ACLTokenRoleLink{ID: roleID})
|
||||
}
|
||||
}
|
||||
|
||||
if c.mergeServiceIdents {
|
||||
for _, svcid := range parsedServiceIdents {
|
||||
found := -1
|
||||
for i, link := range token.ServiceIdentities {
|
||||
for i, link := range t.ServiceIdentities {
|
||||
if link.ServiceName == svcid.ServiceName {
|
||||
found = i
|
||||
break
|
||||
|
@ -251,23 +260,35 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
|
||||
if found != -1 {
|
||||
token.ServiceIdentities[found] = svcid
|
||||
t.ServiceIdentities[found] = svcid
|
||||
} else {
|
||||
token.ServiceIdentities = append(token.ServiceIdentities, svcid)
|
||||
t.ServiceIdentities = append(t.ServiceIdentities, svcid)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
token.ServiceIdentities = parsedServiceIdents
|
||||
t.ServiceIdentities = parsedServiceIdents
|
||||
}
|
||||
|
||||
token, _, err = client.ACL().TokenUpdate(token, nil)
|
||||
t, _, err = client.ACL().TokenUpdate(t, nil)
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to update token %s: %v", tokenID, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.UI.Info("Token updated successfully.")
|
||||
acl.PrintToken(token, c.UI, c.showMeta)
|
||||
formatter, err := token.NewFormatter(c.format, c.showMeta)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
out, err := formatter.FormatToken(t)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if out != "" {
|
||||
c.UI.Info(out)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tokenupdate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -186,3 +187,63 @@ func TestTokenUpdateCommand(t *testing.T) {
|
|||
assert.Equal(legacyToken.SecretID, gotToken.SecretID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenUpdateCommand_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
// Alias because we need to access require package in Retry below
|
||||
req := require.New(t)
|
||||
|
||||
testDir := testutil.TempDir(t, "acl")
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
a := agent.NewTestAgent(t, t.Name(), `
|
||||
primary_datacenter = "dc1"
|
||||
acl {
|
||||
enabled = true
|
||||
tokens {
|
||||
master = "root"
|
||||
}
|
||||
}`)
|
||||
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
|
||||
// Create a policy
|
||||
client := a.Client()
|
||||
|
||||
policy, _, err := client.ACL().PolicyCreate(
|
||||
&api.ACLPolicy{Name: "test-policy"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
req.NoError(err)
|
||||
|
||||
// create a token
|
||||
token, _, err := client.ACL().TokenCreate(
|
||||
&api.ACLToken{Description: "test"},
|
||||
&api.WriteOptions{Token: "root"},
|
||||
)
|
||||
req.NoError(err)
|
||||
|
||||
t.Run("update with policy by name", func(t *testing.T) {
|
||||
cmd := New(ui)
|
||||
args := []string{
|
||||
"-http-addr=" + a.HTTPAddr(),
|
||||
"-id=" + token.AccessorID,
|
||||
"-token=root",
|
||||
"-policy-name=" + policy.Name,
|
||||
"-description=test token",
|
||||
"-format=json",
|
||||
}
|
||||
|
||||
code := cmd.Run(args)
|
||||
assert.Equal(code, 0)
|
||||
assert.Empty(ui.ErrorWriter.String())
|
||||
|
||||
var jsonOutput json.RawMessage
|
||||
err := json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
|
||||
require.NoError(t, err, "token unmarshalling error")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -42,6 +42,8 @@ Usage: `consul acl auth-method create [options] [args]`
|
|||
used to access the TokenReview API to validate other JWTs during login. This
|
||||
flag is required for `-type=kubernetes`.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -23,6 +23,8 @@ Usage: `consul acl auth-method list`
|
|||
|
||||
* `-meta` - Indicates that auth method metadata such as the raft indices should
|
||||
be shown for each entry.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ Usage: `consul acl auth-method read [options] [args]`
|
|||
|
||||
* `-name=<string>` - The name of the auth method to read.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -46,6 +46,8 @@ Usage: `consul acl auth-method update [options] [args]`
|
|||
* `-no-merge` - Do not merge the current auth method information with what is provided
|
||||
to the command. Instead overwrite all fields with the exception of the auth method
|
||||
ID which is immutable.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ Usage: `consul acl binding-rule create [options] [args]`
|
|||
* `-selector=<string>` - Selector is an expression that matches against
|
||||
verified identity attributes returned from the auth method during login.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -24,6 +24,8 @@ Usage: `consul acl binding-rule list`
|
|||
* `-meta` - Indicates that binding rule metadata such as the raft indices
|
||||
should be shown for each entry.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -27,6 +27,8 @@ Usage: `consul acl binding-rule read [options] [args]`
|
|||
* `-meta` - Indicates that binding rule metadata such as the raft
|
||||
indices should be shown for each entry.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -44,6 +44,8 @@ Usage: `consul acl binding-rule update [options] [args]`
|
|||
* `-selector=<string>` - Selector is an expression that matches against
|
||||
verified identity attributes returned from the auth method during login.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -24,6 +24,9 @@ Usage: `consul acl bootstrap [options]`
|
|||
<%= partial "docs/commands/http_api_options_client" %>
|
||||
<%= partial "docs/commands/http_api_options_server" %>
|
||||
|
||||
#### Command Options
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
The output looks like this:
|
||||
|
||||
```text
|
||||
|
|
|
@ -55,6 +55,8 @@ Usage: `consul acl policy create [options] [args]`
|
|||
* `-valid-datacenter=<value>` - Datacenter that the policy should be valid within.
|
||||
This flag may be specified multiple times.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -24,6 +24,8 @@ Usage: `consul acl policy list`
|
|||
* `-meta` - Indicates that policy metadata such as the content hash and
|
||||
Raft indices should be shown for each entry.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -29,6 +29,8 @@ Usage: `consul acl policy read [options] [args]`
|
|||
|
||||
* `-name=<string>` - The name of the policy to read.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -46,6 +46,8 @@ Usage: `consul acl policy update [options] [args]`
|
|||
* `-valid-datacenter=<value>` - Datacenter that the policy should be valid within.
|
||||
This flag may be specified multiple times.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -38,6 +38,8 @@ Usage: `consul acl role create [options] [args]`
|
|||
role. May be specified multiple times. Format is the `SERVICENAME` or
|
||||
`SERVICENAME:DATACENTER1,DATACENTER2,...`
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -24,6 +24,8 @@ Usage: `consul acl role list`
|
|||
* `-meta` - Indicates that role metadata such as the content hash and
|
||||
Raft indices should be shown for each entry.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -29,6 +29,8 @@ Usage: `consul acl role read [options] [args]`
|
|||
|
||||
* `-name=<string>` - The name of the role to read.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -49,6 +49,8 @@ Usage: `consul acl role update [options] [args]`
|
|||
role. May be specified multiple times. Format is the `SERVICENAME` or
|
||||
`SERVICENAME:DATACENTER1,DATACENTER2,...`
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -28,6 +28,8 @@ Usage: `consul acl token clone [options]`
|
|||
Accessor IDs. The special value of 'anonymous' may be provided instead of
|
||||
the anonymous tokens accessor ID
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -52,6 +52,8 @@ Usage: `consul acl token create [options] [args]`
|
|||
**Note**: The SecretID is used to authorize operations against Consul and should
|
||||
be generated from an appropriate cryptographic source.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -24,6 +24,8 @@ Usage: `consul acl token list`
|
|||
* `-meta` - Indicates that token metadata such as the content hash and
|
||||
Raft indices should be shown for each entry.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -30,6 +30,8 @@ Usage: `consul acl token read [options] [args]`
|
|||
* `-self` - Indicates that the current HTTP token should be read by secret ID
|
||||
instead of expecting a -id option.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
|
@ -59,6 +59,8 @@ token
|
|||
migration](https://learn.hashicorp.com/consul/day-2-agent-authentication/migrate-acl-tokens)
|
||||
guide.
|
||||
|
||||
* `-format={pretty|json}` - Command output format. The default value is `pretty`.
|
||||
|
||||
#### Enterprise Options
|
||||
|
||||
<%= partial "docs/commands/http_api_namespace_options" %>
|
||||
|
|
Loading…
Reference in New Issue