Skip to content

Commit e044a1d

Browse files
Add support -f output format for project variables view (#555)
1 parent 516b63e commit e044a1d

File tree

1 file changed

+198
-62
lines changed
  • pkg/cmd/project/variables/view

1 file changed

+198
-62
lines changed
Lines changed: 198 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package view
22

33
import (
4+
"bytes"
45
"fmt"
6+
"io"
7+
"strconv"
8+
"strings"
9+
510
"github.com/MakeNowJust/heredoc/v2"
611
"github.com/OctopusDeploy/cli/pkg/apiclient"
712
variableShared "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/shared"
@@ -11,18 +16,17 @@ import (
1116
"github.com/OctopusDeploy/cli/pkg/util"
1217
"github.com/OctopusDeploy/cli/pkg/util/flag"
1318
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
19+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
1420
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/resources"
1521
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
22+
"github.com/pkg/browser"
1623
"github.com/spf13/cobra"
17-
"io"
18-
"strconv"
19-
"strings"
2024
)
2125

2226
const (
2327
FlagProject = "project"
24-
FlagWeb = "web"
2528
FlagId = "id"
29+
FlagWeb = "web"
2630
)
2731

2832
type ViewFlags struct {
@@ -32,11 +36,12 @@ type ViewFlags struct {
3236
}
3337

3438
type ViewOptions struct {
35-
Client *client.Client
36-
Host string
37-
out io.Writer
38-
name string
39-
*ViewFlags
39+
Client *client.Client
40+
Host string
41+
out io.Writer
42+
name string
43+
flags *ViewFlags
44+
Command *cobra.Command
4045
}
4146

4247
func NewViewFlags() *ViewFlags {
@@ -55,7 +60,7 @@ func NewCmdView(f factory.Factory) *cobra.Command {
5560
Long: "View all values of a project variable in Octopus Deploy",
5661
Example: heredoc.Docf(`
5762
$ %[1]s project variable view
58-
$ %[1]s project variable view DatabaseName --project "Vet Clinic"
63+
$ %[1]s project variable view DatabaseName --project "Vet Clinic"
5964
`, constants.ExecutableName),
6065
Aliases: []string{"ls"},
6166
RunE: func(cmd *cobra.Command, args []string) error {
@@ -74,6 +79,7 @@ func NewCmdView(f factory.Factory) *cobra.Command {
7479
cmd.OutOrStdout(),
7580
args[0],
7681
viewFlags,
82+
cmd,
7783
}
7884

7985
return viewRun(opts)
@@ -88,8 +94,15 @@ func NewCmdView(f factory.Factory) *cobra.Command {
8894
return cmd
8995
}
9096

97+
type VariableValueWithScope struct {
98+
Variable *variables.Variable
99+
ScopeValues *variables.VariableScopeValues
100+
VariableName string
101+
Project *projects.Project
102+
}
103+
91104
func viewRun(opts *ViewOptions) error {
92-
project, err := opts.Client.Projects.GetByIdentifier(opts.Project.Value)
105+
project, err := opts.Client.Projects.GetByIdentifier(opts.flags.Project.Value)
93106
if err != nil {
94107
return err
95108
}
@@ -102,8 +115,8 @@ func viewRun(opts *ViewOptions) error {
102115
filteredVars := util.SliceFilter(
103116
allVars.Variables,
104117
func(variable *variables.Variable) bool {
105-
if opts.Id.Value != "" {
106-
return strings.EqualFold(variable.Name, opts.name) && strings.EqualFold(variable.ID, opts.Id.Value)
118+
if opts.flags.Id.Value != "" {
119+
return strings.EqualFold(variable.Name, opts.name) && strings.EqualFold(variable.ID, opts.flags.Id.Value)
107120
}
108121

109122
return strings.EqualFold(variable.Name, opts.name)
@@ -113,68 +126,191 @@ func viewRun(opts *ViewOptions) error {
113126
return fmt.Errorf("cannot find variable '%s'", opts.name)
114127
}
115128

116-
fmt.Fprintln(opts.out, output.Bold(filteredVars[0].Name))
117-
129+
// Build enriched variable values with scope information
130+
var enrichedVars []*VariableValueWithScope
118131
for _, v := range filteredVars {
119-
data := []*output.DataRow{}
120-
121-
data = append(data, output.NewDataRow("Id", output.Dim(v.GetID())))
122-
if v.IsSensitive {
123-
data = append(data, output.NewDataRow("Value", output.Bold("*** (sensitive)")))
124-
} else {
125-
data = append(data, output.NewDataRow("Value", output.Bold(v.Value)))
126-
}
127-
128-
if v.Description == "" {
129-
v.Description = constants.NoDescription
130-
}
131-
data = append(data, output.NewDataRow("Description", output.Dim(v.Description)))
132-
133132
scopeValues, err := variableShared.ToScopeValues(v, allVars.ScopeValues)
134133
if err != nil {
135134
return err
136135
}
137-
data = addScope(scopeValues.Environments, "Environment scope", data, nil)
138-
data = addScope(scopeValues.Roles, "Role scope", data, nil)
139-
data = addScope(scopeValues.Channels, "Channel scope", data, nil)
140-
data = addScope(scopeValues.Machines, "Machine scope", data, nil)
141-
data = addScope(scopeValues.TenantTags, "Tenant tag scope", data, func(item *resources.ReferenceDataItem) string {
142-
return item.ID
136+
enrichedVars = append(enrichedVars, &VariableValueWithScope{
137+
Variable: v,
138+
ScopeValues: scopeValues,
139+
VariableName: filteredVars[0].Name,
140+
Project: project,
143141
})
144-
data = addScope(scopeValues.Actions, "Step scope", data, nil)
145-
data = addScope(
146-
util.SliceTransform(scopeValues.Processes, func(item *resources.ProcessReferenceDataItem) *resources.ReferenceDataItem {
147-
return &resources.ReferenceDataItem{
148-
ID: item.ID,
149-
Name: item.Name,
150-
}
151-
}),
152-
"Process scope",
153-
data,
154-
nil)
155-
156-
if v.Prompt != nil {
157-
data = append(data, output.NewDataRow("Prompted", "true"))
158-
data = append(data, output.NewDataRow("Prompt Label", v.Prompt.Label))
159-
data = append(data, output.NewDataRow("Prompt Description", output.Dim(v.Prompt.Description)))
160-
data = append(data, output.NewDataRow("Prompt Required", strconv.FormatBool(v.Prompt.IsRequired)))
161-
}
142+
}
143+
144+
return output.PrintArray(enrichedVars, opts.Command, output.Mappers[*VariableValueWithScope]{
145+
Json: func(item *VariableValueWithScope) any {
146+
return getVariableValueAsJson(opts, item)
147+
},
148+
Table: output.TableDefinition[*VariableValueWithScope]{
149+
Header: []string{"ID", "VALUE", "DESCRIPTION", "SCOPES"},
150+
Row: func(item *VariableValueWithScope) []string {
151+
return getVariableValueAsTableRow(item)
152+
},
153+
},
154+
Basic: func(item *VariableValueWithScope) string {
155+
return formatVariableValueForBasic(opts, item)
156+
},
157+
})
158+
}
159+
160+
type VariableValueAsJson struct {
161+
Id string `json:"Id"`
162+
Name string `json:"Name"`
163+
Value string `json:"Value"`
164+
IsSensitive bool `json:"IsSensitive"`
165+
Description string `json:"Description"`
166+
Scope *variables.VariableScopeValues `json:"Scope"`
167+
Prompt *variables.VariablePromptOptions `json:"Prompt,omitempty"`
168+
WebUrl string `json:"WebUrl"`
169+
}
162170

163-
fmt.Fprintln(opts.out)
164-
output.PrintRows(data, opts.out)
171+
func getVariableValueAsJson(opts *ViewOptions, item *VariableValueWithScope) VariableValueAsJson {
172+
description := item.Variable.Description
173+
if description == "" {
174+
description = constants.NoDescription
165175
}
166176

167-
return nil
177+
return VariableValueAsJson{
178+
Id: item.Variable.GetID(),
179+
Name: item.VariableName,
180+
Value: item.Variable.Value,
181+
IsSensitive: item.Variable.IsSensitive,
182+
Description: description,
183+
Scope: item.ScopeValues,
184+
Prompt: item.Variable.Prompt,
185+
WebUrl: util.GenerateWebURL(opts.Host, item.Project.SpaceID, fmt.Sprintf("projects/%s/variables", item.Project.Slug)),
186+
}
168187
}
169188

170-
func addScope(values []*resources.ReferenceDataItem, scopeDescription string, data []*output.DataRow, displaySelector func(item *resources.ReferenceDataItem) string) []*output.DataRow {
171-
if displaySelector == nil {
172-
displaySelector = func(item *resources.ReferenceDataItem) string { return item.Name }
189+
func getVariableValueAsTableRow(item *VariableValueWithScope) []string {
190+
v := item.Variable
191+
scopeValues := item.ScopeValues
192+
193+
var value string
194+
if v.IsSensitive {
195+
value = "*** (sensitive)"
196+
} else {
197+
value = v.Value
198+
}
199+
200+
description := v.Description
201+
if description == "" {
202+
description = constants.NoDescription
203+
}
204+
205+
// Build scope summary
206+
var scopeParts []string
207+
if util.Any(scopeValues.Environments) {
208+
scopeParts = append(scopeParts, fmt.Sprintf("Env: %s", formatScopeList(scopeValues.Environments, nil)))
209+
}
210+
if util.Any(scopeValues.Roles) {
211+
scopeParts = append(scopeParts, fmt.Sprintf("Role: %s", formatScopeList(scopeValues.Roles, nil)))
212+
}
213+
if util.Any(scopeValues.Channels) {
214+
scopeParts = append(scopeParts, fmt.Sprintf("Channel: %s", formatScopeList(scopeValues.Channels, nil)))
215+
}
216+
if util.Any(scopeValues.Machines) {
217+
scopeParts = append(scopeParts, fmt.Sprintf("Machine: %s", formatScopeList(scopeValues.Machines, nil)))
218+
}
219+
if util.Any(scopeValues.TenantTags) {
220+
scopeParts = append(scopeParts, fmt.Sprintf("Tag: %s", formatScopeList(scopeValues.TenantTags, func(item *resources.ReferenceDataItem) string {
221+
return item.ID
222+
})))
223+
}
224+
if util.Any(scopeValues.Actions) {
225+
scopeParts = append(scopeParts, fmt.Sprintf("Step: %s", formatScopeList(scopeValues.Actions, nil)))
226+
}
227+
if util.Any(scopeValues.Processes) {
228+
scopeParts = append(scopeParts, fmt.Sprintf("Process: %s", formatProcessList(scopeValues.Processes)))
229+
}
230+
231+
scopes := strings.Join(scopeParts, "; ")
232+
if scopes == "" {
233+
scopes = "No scopes"
234+
}
235+
236+
return []string{
237+
output.Dim(v.GetID()),
238+
value,
239+
description,
240+
scopes,
241+
}
242+
}
243+
244+
func formatVariableValueForBasic(opts *ViewOptions, item *VariableValueWithScope) string {
245+
v := item.Variable
246+
scopeValues := item.ScopeValues
247+
248+
data := []*output.DataRow{}
249+
250+
data = append(data, output.NewDataRow("Id", output.Dim(v.GetID())))
251+
if v.IsSensitive {
252+
data = append(data, output.NewDataRow("Value", output.Bold("*** (sensitive)")))
253+
} else {
254+
data = append(data, output.NewDataRow("Value", output.Bold(v.Value)))
255+
}
256+
257+
if v.Description == "" {
258+
v.Description = constants.NoDescription
259+
}
260+
data = append(data, output.NewDataRow("Description", output.Dim(v.Description)))
261+
262+
if util.Any(scopeValues.Environments) {
263+
data = append(data, output.NewDataRow("Environment scope", output.FormatAsList(util.SliceTransform(scopeValues.Environments, func(item *resources.ReferenceDataItem) string { return item.Name }))))
264+
}
265+
if util.Any(scopeValues.Roles) {
266+
data = append(data, output.NewDataRow("Role scope", output.FormatAsList(util.SliceTransform(scopeValues.Roles, func(item *resources.ReferenceDataItem) string { return item.Name }))))
267+
}
268+
if util.Any(scopeValues.Channels) {
269+
data = append(data, output.NewDataRow("Channel scope", output.FormatAsList(util.SliceTransform(scopeValues.Channels, func(item *resources.ReferenceDataItem) string { return item.Name }))))
270+
}
271+
if util.Any(scopeValues.Machines) {
272+
data = append(data, output.NewDataRow("Machine scope", output.FormatAsList(util.SliceTransform(scopeValues.Machines, func(item *resources.ReferenceDataItem) string { return item.Name }))))
273+
}
274+
if util.Any(scopeValues.TenantTags) {
275+
data = append(data, output.NewDataRow("Tenant tag scope", output.FormatAsList(util.SliceTransform(scopeValues.TenantTags, func(item *resources.ReferenceDataItem) string { return item.ID }))))
276+
}
277+
if util.Any(scopeValues.Actions) {
278+
data = append(data, output.NewDataRow("Step scope", output.FormatAsList(util.SliceTransform(scopeValues.Actions, func(item *resources.ReferenceDataItem) string { return item.Name }))))
279+
}
280+
if util.Any(scopeValues.Processes) {
281+
data = append(data, output.NewDataRow("Process scope", output.FormatAsList(util.SliceTransform(scopeValues.Processes, func(item *resources.ProcessReferenceDataItem) string { return item.Name }))))
282+
}
283+
284+
if v.Prompt != nil {
285+
data = append(data, output.NewDataRow("Prompted", "true"))
286+
data = append(data, output.NewDataRow("Prompt Label", v.Prompt.Label))
287+
data = append(data, output.NewDataRow("Prompt Description", output.Dim(v.Prompt.Description)))
288+
data = append(data, output.NewDataRow("Prompt Required", strconv.FormatBool(v.Prompt.IsRequired)))
173289
}
174290

175-
if util.Any(values) {
176-
data = append(data, output.NewDataRow(scopeDescription, output.FormatAsList(util.SliceTransform(values, displaySelector))))
291+
var buf bytes.Buffer
292+
fmt.Fprintf(&buf, "%s\n\n", output.Bold(item.VariableName))
293+
output.PrintRows(data, &buf)
294+
295+
url := util.GenerateWebURL(opts.Host, item.Project.SpaceID, fmt.Sprintf("projects/%s/variables", item.Project.Slug))
296+
fmt.Fprintf(&buf, "\nView this project's variables in Octopus Deploy: %s\n", output.Blue(url))
297+
298+
if opts.flags.Web.Value {
299+
browser.OpenURL(url)
177300
}
178301

179-
return data
302+
return buf.String()
303+
}
304+
305+
func formatScopeList(values []*resources.ReferenceDataItem, displaySelector func(item *resources.ReferenceDataItem) string) string {
306+
if displaySelector == nil {
307+
displaySelector = func(item *resources.ReferenceDataItem) string { return item.Name }
308+
}
309+
return strings.Join(util.SliceTransform(values, displaySelector), ", ")
310+
}
311+
312+
func formatProcessList(processes []*resources.ProcessReferenceDataItem) string {
313+
return strings.Join(util.SliceTransform(processes, func(item *resources.ProcessReferenceDataItem) string {
314+
return item.Name
315+
}), ", ")
180316
}

0 commit comments

Comments
 (0)