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:
Matt Keeler 2020-03-26 13:16:21 -04:00 committed by GitHub
commit e873dbe111
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 2568 additions and 365 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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()

View File

@ -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
}

View File

@ -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

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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()

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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()

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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")
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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")
})
}

View File

@ -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" %>

View File

@ -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

View File

@ -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" %>

View File

@ -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

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>

View File

@ -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" %>