11package view
22
33import (
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
2226const (
2327 FlagProject = "project"
24- FlagWeb = "web"
2528 FlagId = "id"
29+ FlagWeb = "web"
2630)
2731
2832type ViewFlags struct {
@@ -32,11 +36,12 @@ type ViewFlags struct {
3236}
3337
3438type 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
4247func 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+
91104func 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 , "\n View 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