Skip to content

Commit 69b7792

Browse files
authored
feat(core): enable styled and json default outputs (#66)
1. Adds a `tructl.yaml` config to drive things like global CLI output format 2. Adds global command `tructl config output --format <'json' | 'styled'>` that updates the config file 3. Maintains support for global command `--json` to output a single run of the CLI as json but not overwrite the config 4. Reads the `--json` and config for output format 5. Unifies success handling throughout all existing policy flows to print styled output or JSON as applicable Closes #30
1 parent b001351 commit 69b7792

23 files changed

+743
-542
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ The main goals are to:
1010

1111
## TODO list
1212

13-
- [ ] Add support for `--json` persistent flag
1413
- [ ] Add support for json input as piped input
1514
- [ ] Add help level handler for each command
1615
- [ ] Add support for `--verbose` persistent flag
@@ -28,13 +27,14 @@ The CLI is built using [cobra](https://cobra.dev/).
2827

2928
The primary function is to support CRUD operations using commands as arguments and flags as the values.
3029

30+
The output format (currently `styled` or `json`) is configurable in the `tructl.yaml` or via CLI flag.
31+
3132
#### To add a command
3233

3334
1. Capture the flag value and validate the values
3435
1. Alt support JSON input as piped input
3536
2. Run the handler which is located in `pkg/handlers` and pass the values as arguments
3637
3. Handle any errors and return the result in a lite TUI format
37-
1. Alt support JSON output when `--json` flag is passed
3838

3939
### TUI
4040

cmd/config.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/opentdf/tructl/internal/config"
7+
"github.com/opentdf/tructl/pkg/cli"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
// configCmd is the command for managing configuration
12+
var configCmd = &cobra.Command{
13+
Use: "config",
14+
Short: "Manage configuration",
15+
Long: `
16+
Manage configuration within 'tructl'.
17+
18+
Configuration is used to manage the configuration of the 'tructl' command line tool and updates the
19+
config .yaml file in the root directory when changes have been made.
20+
`,
21+
}
22+
23+
var updateOutputFormatCmd = &cobra.Command{
24+
Use: "output",
25+
Short: "Define the configured output format",
26+
Long: `
27+
Define the configured output format for the 'tructl' command line tool. The only supported outputs at
28+
this time are 'json' and styled CLI output, which is the default when unspecified.
29+
`,
30+
Run: func(cmd *cobra.Command, args []string) {
31+
h := cli.NewHandler(cmd)
32+
defer h.Close()
33+
34+
flagHelper := cli.NewFlagHelper(cmd)
35+
format := flagHelper.GetRequiredString("format")
36+
37+
config.UpdateOutputFormat(format)
38+
fmt.Println(cli.SuccessMessage(fmt.Sprintf("Output format updated to %s", format)))
39+
},
40+
}
41+
42+
func init() {
43+
updateOutputFormatCmd.Flags().String("format", "", "'json' or 'styled' as the configured output format")
44+
configCmd.AddCommand(updateOutputFormatCmd)
45+
rootCmd.AddCommand(configCmd)
46+
}

cmd/dev.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"encoding/json"
55
"fmt"
66

7+
"github.com/charmbracelet/lipgloss/table"
78
"github.com/opentdf/platform/protocol/go/common"
9+
"github.com/opentdf/tructl/internal/config"
810
"github.com/opentdf/tructl/pkg/cli"
911
"github.com/spf13/cobra"
1012
)
@@ -59,9 +61,6 @@ func getMetadataRows(m *common.Metadata) [][]string {
5961
}
6062
metadataRows = append(metadataRows, []string{"Labels", cli.CommaSeparated(labelRows)})
6163
}
62-
if m.Description != "" {
63-
metadataRows = append(metadataRows, []string{"Description", m.Description})
64-
}
6564
return metadataRows
6665
}
6766
return nil
@@ -78,6 +77,19 @@ func unMarshalMetadata(m string) *common.MetadataMutable {
7877
return nil
7978
}
8079

80+
// HandleSuccess prints a success message according to the configured format (styled table or JSON)
81+
func HandleSuccess(command *cobra.Command, id string, t *table.Table, policyObject interface{}) {
82+
if TructlCfg.Output.Format == config.OutputJSON || configFlagOverrides.OutputFormatJSON {
83+
if output, err := json.MarshalIndent(policyObject, "", " "); err != nil {
84+
cli.ExitWithError("Error marshalling policy object", err)
85+
} else {
86+
fmt.Println(string(output))
87+
}
88+
return
89+
}
90+
cli.PrintSuccessTable(command, id, t)
91+
}
92+
8193
func init() {
8294
rootCmd.AddCommand(devCmd)
8395
devCmd.AddCommand(designCmd)

cmd/policy-attributes.go

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"fmt"
55
"strings"
66

7-
"github.com/opentdf/platform/protocol/go/policy/attributes"
7+
"github.com/opentdf/platform/protocol/go/policy"
88
"github.com/opentdf/tructl/pkg/cli"
99
"github.com/spf13/cobra"
1010
)
@@ -51,7 +51,7 @@ used to define the access controls based on subject encodings and entity entitle
5151
}
5252

5353
// create attribute values
54-
attrValues := make([]*attributes.Value, 0, len(values))
54+
attrValues := make([]*policy.Value, 0, len(values))
5555
valueErrors := make(map[string]error)
5656
for _, value := range values {
5757
v, err := h.CreateAttributeValue(attr.Id, value)
@@ -61,30 +61,28 @@ used to define the access controls based on subject encodings and entity entitle
6161
attrValues = append(attrValues, v)
6262
}
6363

64-
a := cli.GetSimpleAttribute(&attributes.Attribute{
64+
a := cli.GetSimpleAttribute(&policy.Attribute{
6565
Id: attr.Id,
6666
Name: attr.Name,
6767
Rule: attr.Rule,
6868
Values: attrValues,
6969
Namespace: attr.Namespace,
7070
})
7171

72-
fmt.Println(cli.SuccessMessage("Attribute created"))
73-
fmt.Println(
74-
cli.NewTabular().Rows([][]string{
75-
{"Name", a.Name},
76-
{"Rule", a.Rule},
77-
{"Values", cli.CommaSeparated(a.Values)},
78-
{"Namespace", a.Namespace},
79-
}...).Render(),
80-
)
72+
t := cli.NewTabular().Rows([][]string{
73+
{"Name", a.Name},
74+
{"Rule", a.Rule},
75+
{"Values", cli.CommaSeparated(a.Values)},
76+
{"Namespace", a.Namespace},
77+
}...)
8178

8279
if len(valueErrors) > 0 {
8380
fmt.Println(cli.ErrorMessage("Error creating attribute values", nil))
8481
for value, err := range valueErrors {
8582
cli.ErrorMessage(value, err)
8683
}
8784
}
85+
HandleSuccess(cmd, a.Id, t, a)
8886
},
8987
}
9088

@@ -107,17 +105,15 @@ used to define the access controls based on subject encodings and entity entitle
107105
}
108106

109107
a := cli.GetSimpleAttribute(attr)
110-
fmt.Println(cli.SuccessMessage("Attribute found"))
111-
fmt.Println(
112-
cli.NewTabular().
113-
Rows([][]string{
114-
{"Id", a.Id},
115-
{"Name", a.Name},
116-
{"Rule", a.Rule},
117-
{"Values", cli.CommaSeparated(a.Values)},
118-
{"Namespace", a.Namespace},
119-
}...).Render(),
120-
)
108+
t := cli.NewTabular().
109+
Rows([][]string{
110+
{"Id", a.Id},
111+
{"Name", a.Name},
112+
{"Rule", a.Rule},
113+
{"Values", cli.CommaSeparated(a.Values)},
114+
{"Namespace", a.Namespace},
115+
}...)
116+
HandleSuccess(cmd, a.Id, t, a)
121117
},
122118
}
123119

@@ -146,7 +142,7 @@ used to define the access controls based on subject encodings and entity entitle
146142
cli.CommaSeparated(a.Values),
147143
)
148144
}
149-
fmt.Println(t.Render())
145+
HandleSuccess(cmd, "", t, attrs)
150146
},
151147
}
152148

@@ -177,16 +173,14 @@ used to define the access controls based on subject encodings and entity entitle
177173
}
178174

179175
a := cli.GetSimpleAttribute(attr)
180-
fmt.Println(cli.SuccessMessage("Attribute deactivated"))
181-
fmt.Println(
182-
cli.NewTabular().
183-
Rows([][]string{
184-
{"Name", a.Name},
185-
{"Rule", a.Rule},
186-
{"Values", cli.CommaSeparated(a.Values)},
187-
{"Namespace", a.Namespace},
188-
}...).Render(),
189-
)
176+
t := cli.NewTabular().
177+
Rows([][]string{
178+
{"Name", a.Name},
179+
{"Rule", a.Rule},
180+
{"Values", cli.CommaSeparated(a.Values)},
181+
{"Namespace", a.Namespace},
182+
}...)
183+
HandleSuccess(cmd, a.Id, t, a)
190184
},
191185
}
192186

@@ -201,10 +195,10 @@ used to define the access controls based on subject encodings and entity entitle
201195
flagHelper := cli.NewFlagHelper(cmd)
202196
id := flagHelper.GetRequiredString("id")
203197

204-
if _, err := h.UpdateAttribute(id); err != nil {
198+
if a, err := h.UpdateAttribute(id); err != nil {
205199
cli.ExitWithError("Could not update attribute", err)
206200
} else {
207-
fmt.Println(cli.SuccessMessage(fmt.Sprintf("Attribute id: %s updated.", id)))
201+
HandleSuccess(cmd, id, nil, a)
208202
}
209203
},
210204
}

cmd/policy-namespaces.go

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,12 @@ or different attributes tied to each.
4646
cli.ExitWithError(errMsg, err)
4747
}
4848

49-
fmt.Println(cli.SuccessMessage("Namespace found"))
50-
fmt.Println(
51-
cli.NewTabular().
52-
Rows([][]string{
53-
{"Id", ns.Id},
54-
{"Name", ns.Name},
55-
}...).Render(),
56-
)
49+
t := cli.NewTabular().
50+
Rows([][]string{
51+
{"Id", ns.Id},
52+
{"Name", ns.Name},
53+
}...)
54+
HandleSuccess(cmd, ns.Id, t, ns)
5755
},
5856
}
5957

@@ -77,7 +75,7 @@ or different attributes tied to each.
7775
ns.Name,
7876
)
7977
}
80-
fmt.Println(t.Render())
78+
HandleSuccess(cmd, "", t, list)
8179
},
8280
}
8381

@@ -96,13 +94,11 @@ or different attributes tied to each.
9694
cli.ExitWithError("Could not create namespace", err)
9795
}
9896

99-
fmt.Println(cli.SuccessMessage("Namespace created"))
100-
fmt.Println(
101-
cli.NewTabular().Rows([][]string{
102-
{"Name", name},
103-
{"Id", created.Id},
104-
}...).Render(),
105-
)
97+
t := cli.NewTabular().Rows([][]string{
98+
{"Name", name},
99+
{"Id", created.Id},
100+
}...)
101+
HandleSuccess(cmd, created.Id, t, created)
106102
},
107103
}
108104

@@ -131,14 +127,12 @@ or different attributes tied to each.
131127
cli.ExitWithError(errMsg, err)
132128
}
133129

134-
fmt.Println(cli.SuccessMessage("Namespace deactivated"))
135-
fmt.Println(
136-
cli.NewTabular().
137-
Rows([][]string{
138-
{"Id", ns.Id},
139-
{"Name", ns.Name},
140-
}...).Render(),
141-
)
130+
t := cli.NewTabular().
131+
Rows([][]string{
132+
{"Id", ns.Id},
133+
{"Name", ns.Name},
134+
}...)
135+
HandleSuccess(cmd, ns.Id, t, ns)
142136
},
143137
}
144138

@@ -155,13 +149,18 @@ or different attributes tied to each.
155149
id := flagHelper.GetRequiredString("id")
156150
name := flagHelper.GetRequiredString("name")
157151

158-
if _, err := h.UpdateNamespace(
152+
ns, err := h.UpdateNamespace(
159153
id,
160154
name,
161-
); err != nil {
155+
)
156+
if err != nil {
162157
cli.ExitWithError("Could not update namespace", err)
163158
}
164-
fmt.Println(cli.SuccessMessage(fmt.Sprintf("Namespace id: (%s) updated. Name set to (%s).", id, name)))
159+
t := cli.NewTabular().Rows([][]string{
160+
{"Id", ns.Id},
161+
{"Name", ns.Name},
162+
}...)
163+
HandleSuccess(cmd, id, t, ns)
165164
},
166165
}
167166
)

0 commit comments

Comments
 (0)