Skip to content

Commit 6592cb8

Browse files
List scoped common variables (#488)
* List scoped common variables * Separate line per environment * Fix default and sensitive value display * Update naming for consistency with client * Add shared feature toggle method * Update the client version * Handle errors in feature toggle function
1 parent d7d79ee commit 6592cb8

File tree

4 files changed

+245
-50
lines changed

4 files changed

+245
-50
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/AlecAivazis/survey/v2 v2.3.7
77
github.com/MakeNowJust/heredoc/v2 v2.0.1
88
github.com/OctopusDeploy/go-octodiff v1.0.0
9-
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.64.3
9+
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.64.4
1010
github.com/bmatcuk/doublestar/v4 v4.4.0
1111
github.com/briandowns/spinner v1.19.0
1212
github.com/google/uuid v1.3.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4
4848
github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU=
4949
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.64.3 h1:WLvvQGg7TXECnq3m/yawwYVaASfRaIUn8ndwMvkxiaw=
5050
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.64.3/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
51+
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.64.4 h1:tC8Wc31r9BPIkzEo1LF8chOG7ehGOpViVnQAALprNss=
52+
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.64.4/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
5153
github.com/bmatcuk/doublestar/v4 v4.4.0 h1:LmAwNwhjEbYtyVLzjcP/XeVw4nhuScHGkF/XWXnvIic=
5254
github.com/bmatcuk/doublestar/v4 v4.4.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
5355
github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E=

pkg/cmd/tenant/variables/list/list.go

Lines changed: 210 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import (
88
"github.com/OctopusDeploy/cli/pkg/factory"
99
"github.com/OctopusDeploy/cli/pkg/output"
1010
"github.com/OctopusDeploy/cli/pkg/question/selectors"
11+
"github.com/OctopusDeploy/cli/pkg/util/featuretoggle"
1112
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/actiontemplates"
1213
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
1314
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
15+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants"
1416
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/variables"
1517
"github.com/spf13/cobra"
18+
"sort"
1619
)
1720

1821
const (
@@ -99,67 +102,169 @@ func listRun(cmd *cobra.Command, f factory.Factory, id string) error {
99102
return err
100103
}
101104

102-
vars, err := client.Tenants.GetVariables(tenant)
103-
if err != nil {
104-
return err
105-
}
105+
toggleValue, err := featuretoggle.IsToggleEnabled(client, "CommonVariableScopingFeatureToggle")
106106

107-
missingVariablesResponse, err := client.Tenants.GetMissingVariables(variables.MissingVariablesQuery{TenantID: vars.TenantID})
108-
var missingVariables []variables.MissingVariable
109-
if len(*missingVariablesResponse) > 0 {
110-
missingVariables = (*missingVariablesResponse)[0].MissingVariables
111-
}
112-
var allVariableValues []*VariableValue
113-
for _, element := range vars.LibraryVariables {
107+
if toggleValue {
108+
projectVariablesQuery := variables.GetTenantProjectVariablesQuery{
109+
TenantID: tenant.ID,
110+
SpaceID: tenant.SpaceID,
111+
IncludeMissingVariables: true,
112+
}
114113

115-
variableValues := unwrapCommonVariables(element, missingVariables)
116-
for _, v := range variableValues {
117-
allVariableValues = append(allVariableValues, v)
114+
projectVariables, err := tenants.GetProjectVariables(client, projectVariablesQuery)
115+
if err != nil {
116+
return err
118117
}
119-
}
120118

121-
if err != nil {
122-
return err
123-
}
124-
environmentMap, err := getEnvironmentMap(client)
125-
if err != nil {
126-
return err
127-
}
128-
for _, element := range vars.ProjectVariables {
129-
variableValues := unwrapProjectVariables(element, environmentMap, missingVariables)
130-
for _, v := range variableValues {
131-
allVariableValues = append(allVariableValues, v)
119+
commonVariablesQuery := variables.GetTenantCommonVariablesQuery{
120+
TenantID: tenant.ID,
121+
SpaceID: tenant.SpaceID,
122+
IncludeMissingVariables: true,
132123
}
133-
}
134124

135-
return output.PrintArray(allVariableValues, cmd, output.Mappers[*VariableValue]{
136-
Json: func(item *VariableValue) any {
137-
if item.Type == LibraryVariableSetType {
138-
return NewVariableValueAsJson(item)
139-
} else {
140-
return NewVariableValueProjectAsJson(item)
125+
commonVariables, err := tenants.GetCommonVariables(client, commonVariablesQuery)
126+
if err != nil {
127+
return err
128+
}
129+
130+
environmentMap, err := getEnvironmentMap(client)
131+
if err != nil {
132+
return err
133+
}
134+
135+
var allVariableValues []*VariableValue
136+
137+
for _, element := range commonVariables.MissingCommonVariables {
138+
variableValue := unwrapCommonVariables(element, environmentMap)
139+
allVariableValues = append(allVariableValues, variableValue...)
140+
}
141+
142+
for _, element := range commonVariables.CommonVariables {
143+
variableValue := unwrapCommonVariables(element, environmentMap)
144+
allVariableValues = append(allVariableValues, variableValue...)
145+
}
146+
147+
for _, element := range projectVariables.MissingProjectVariables {
148+
variableValue := unwrapProjectVariables(element, environmentMap)
149+
allVariableValues = append(allVariableValues, variableValue...)
150+
}
151+
152+
for _, element := range projectVariables.ProjectVariables {
153+
variableValue := unwrapProjectVariables(element, environmentMap)
154+
allVariableValues = append(allVariableValues, variableValue...)
155+
}
156+
157+
sortVariableOutput(allVariableValues)
158+
159+
return output.PrintArray(allVariableValues, cmd, output.Mappers[*VariableValue]{
160+
Json: func(item *VariableValue) any {
161+
if item.Type == LibraryVariableSetType {
162+
return NewVariableValueAsJson(item)
163+
} else {
164+
return NewVariableValueProjectAsJson(item)
165+
}
166+
},
167+
Table: output.TableDefinition[*VariableValue]{
168+
Header: []string{"NAME", "LABEL", "TYPE", "OWNER", "ENVIRONMENT", "VALUE", "SENSITIVE", "DEFAULT VALUE"},
169+
Row: func(item *VariableValue) []string {
170+
value := item.Value
171+
if item.HasMissingValue {
172+
value = output.Red("<missing>")
173+
}
174+
175+
return []string{output.Bold(item.Name), item.Label, item.Type, item.OwnerName, item.ScopeName, value, fmt.Sprint(item.IsSensitive), fmt.Sprint(item.IsDefaultValue)}
176+
},
177+
},
178+
Basic: func(item *VariableValue) string {
179+
return item.Name
180+
},
181+
})
182+
183+
return nil
184+
} else {
185+
vars, err := client.Tenants.GetVariables(tenant)
186+
if err != nil {
187+
return err
188+
}
189+
190+
missingVariablesResponse, err := client.Tenants.GetMissingVariables(variables.MissingVariablesQuery{TenantID: vars.TenantID})
191+
var missingVariables []variables.MissingVariable
192+
if len(*missingVariablesResponse) > 0 {
193+
missingVariables = (*missingVariablesResponse)[0].MissingVariables
194+
}
195+
var allVariableValues []*VariableValue
196+
for _, element := range vars.LibraryVariables {
197+
198+
variableValues := unwrapCommonVariablesV1(element, missingVariables)
199+
for _, v := range variableValues {
200+
allVariableValues = append(allVariableValues, v)
141201
}
142-
},
143-
Table: output.TableDefinition[*VariableValue]{
144-
Header: []string{"NAME", "LABEL", "TYPE", "OWNER", "ENVIRONMENT", "VALUE", "SENSITIVE", "DEFAULT VALUE"},
145-
Row: func(item *VariableValue) []string {
146-
value := item.Value
147-
if item.HasMissingValue {
148-
value = output.Red("<missing>")
202+
}
203+
204+
if err != nil {
205+
return err
206+
}
207+
environmentMap, err := getEnvironmentMap(client)
208+
if err != nil {
209+
return err
210+
}
211+
for _, element := range vars.ProjectVariables {
212+
variableValues := unwrapProjectVariablesV1(element, environmentMap, missingVariables)
213+
for _, v := range variableValues {
214+
allVariableValues = append(allVariableValues, v)
215+
}
216+
}
217+
218+
return output.PrintArray(allVariableValues, cmd, output.Mappers[*VariableValue]{
219+
Json: func(item *VariableValue) any {
220+
if item.Type == LibraryVariableSetType {
221+
return NewVariableValueAsJson(item)
222+
} else {
223+
return NewVariableValueProjectAsJson(item)
149224
}
225+
},
226+
Table: output.TableDefinition[*VariableValue]{
227+
Header: []string{"NAME", "LABEL", "TYPE", "OWNER", "ENVIRONMENT", "VALUE", "SENSITIVE", "DEFAULT VALUE"},
228+
Row: func(item *VariableValue) []string {
229+
value := item.Value
230+
if item.HasMissingValue {
231+
value = output.Red("<missing>")
232+
}
150233

151-
return []string{output.Bold(item.Name), item.Label, item.Type, item.OwnerName, item.ScopeName, value, fmt.Sprint(item.IsSensitive), fmt.Sprint(item.IsDefaultValue)}
234+
return []string{output.Bold(item.Name), item.Label, item.Type, item.OwnerName, item.ScopeName, value, fmt.Sprint(item.IsSensitive), fmt.Sprint(item.IsDefaultValue)}
235+
},
152236
},
153-
},
154-
Basic: func(item *VariableValue) string {
155-
return item.Name
156-
},
157-
})
237+
Basic: func(item *VariableValue) string {
238+
return item.Name
239+
},
240+
})
158241

159-
return nil
242+
return nil
243+
}
160244
}
161245

162-
func unwrapCommonVariables(variables variables.LibraryVariable, missingVariables []variables.MissingVariable) []*VariableValue {
246+
func sortVariableOutput(allVariableValues []*VariableValue) {
247+
sort.SliceStable(allVariableValues, func(i, j int) bool {
248+
if allVariableValues[i].Type != allVariableValues[j].Type {
249+
return allVariableValues[i].Type < allVariableValues[j].Type
250+
}
251+
if allVariableValues[i].OwnerName != allVariableValues[j].OwnerName {
252+
return allVariableValues[i].OwnerName < allVariableValues[j].OwnerName
253+
}
254+
if allVariableValues[i].Name != allVariableValues[j].Name {
255+
return allVariableValues[i].Name < allVariableValues[j].Name
256+
}
257+
if allVariableValues[i].Value == "" && allVariableValues[j].Value != "" {
258+
return true
259+
}
260+
if allVariableValues[i].Value != "" && allVariableValues[j].Value == "" {
261+
return false
262+
}
263+
return allVariableValues[i].Value < allVariableValues[j].Value
264+
})
265+
}
266+
267+
func unwrapCommonVariablesV1(variables variables.LibraryVariable, missingVariables []variables.MissingVariable) []*VariableValue {
163268
var results []*VariableValue = nil
164269
for _, template := range variables.Templates {
165270
value, isDefault := getVariableValue(template, variables.Variables)
@@ -182,7 +287,7 @@ func unwrapCommonVariables(variables variables.LibraryVariable, missingVariables
182287
return results
183288
}
184289

185-
func unwrapProjectVariables(variables variables.ProjectVariable, environmentMap map[string]string, missingVariables []variables.MissingVariable) []*VariableValue {
290+
func unwrapProjectVariablesV1(variables variables.ProjectVariable, environmentMap map[string]string, missingVariables []variables.MissingVariable) []*VariableValue {
186291
var results []*VariableValue = nil
187292
for _, template := range variables.Templates {
188293
for environmentId, environmentVariables := range variables.Variables {
@@ -207,6 +312,62 @@ func unwrapProjectVariables(variables variables.ProjectVariable, environmentMap
207312
return results
208313
}
209314

315+
func unwrapCommonVariables(variable variables.TenantCommonVariable, environmentMap map[string]string) []*VariableValue {
316+
var results []*VariableValue = nil
317+
var isDefault = variable.ID == ""
318+
var displayValue string
319+
320+
if isDefault {
321+
displayValue = getDisplayValue(variable.Template.DefaultValue)
322+
} else {
323+
displayValue = getDisplayValue(&variable.Value)
324+
}
325+
326+
for _, environmentId := range variable.Scope.EnvironmentIds {
327+
328+
results = append(results, &VariableValue{
329+
Type: LibraryVariableSetType,
330+
OwnerName: variable.LibraryVariableSetName,
331+
Name: variable.Template.Name,
332+
Value: displayValue,
333+
IsSensitive: variable.Value.IsSensitive,
334+
IsDefaultValue: isDefault, // Variables without an ID are sourced from the MissingVariables list. For consistency with V1, IsDefault applies to both default and missing variables
335+
ScopeName: environmentMap[environmentId],
336+
HasMissingValue: isDefault && displayValue == "",
337+
})
338+
}
339+
340+
return results
341+
}
342+
343+
func unwrapProjectVariables(variable variables.TenantProjectVariable, environmentMap map[string]string) []*VariableValue {
344+
var results []*VariableValue = nil
345+
var isDefault = variable.ID == ""
346+
var displayValue string
347+
348+
if isDefault {
349+
displayValue = getDisplayValue(variable.Template.DefaultValue)
350+
} else {
351+
displayValue = getDisplayValue(&variable.Value)
352+
}
353+
354+
for _, environmentId := range variable.Scope.EnvironmentIds {
355+
356+
results = append(results, &VariableValue{
357+
Type: ProjectType,
358+
OwnerName: variable.ProjectName,
359+
Name: variable.Template.Name,
360+
Value: displayValue,
361+
IsSensitive: variable.Value.IsSensitive,
362+
IsDefaultValue: isDefault, // Variables without an ID are sourced from the MissingVariables list. For consistency with V1, IsDefault applies to both default and missing variables
363+
ScopeName: environmentMap[environmentId],
364+
HasMissingValue: isDefault && displayValue == "",
365+
})
366+
}
367+
368+
return results
369+
}
370+
210371
func hasMissingProjectValue(missingVariables []variables.MissingVariable, projectID string, environmentID string, templateID string) bool {
211372
for _, v := range missingVariables {
212373
if v.ProjectID == projectID && v.EnvironmentID == environmentID && v.VariableTemplateID == templateID {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package featuretoggle
2+
3+
import (
4+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
5+
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/configuration"
6+
)
7+
8+
// IsToggleEnabled retrieves an Octopus feature toggle by name. If an error occurs, it returns false.
9+
func IsToggleEnabled(client *client.Client, toggleName string) (bool, error) {
10+
toggleRequest := &configuration.FeatureToggleConfigurationQuery{
11+
Name: toggleName,
12+
}
13+
14+
returnToggleResponse, err := configuration.Get(client, toggleRequest)
15+
16+
if err != nil {
17+
return false, err
18+
}
19+
20+
if len(returnToggleResponse.FeatureToggles) == 0 {
21+
return false, err
22+
}
23+
24+
var toggleValue = returnToggleResponse.FeatureToggles[0]
25+
26+
// Verify name to avoid error in older versions of Octopus where Name param is not recognised
27+
if toggleValue.Name != toggleName {
28+
return false, err
29+
}
30+
31+
return toggleValue.IsEnabled, err
32+
}

0 commit comments

Comments
 (0)