-
Notifications
You must be signed in to change notification settings - Fork 0
/
deploy.go
189 lines (163 loc) · 6.22 KB
/
deploy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Jackal Authors
// Package filters contains core implementations of the ComponentFilterStrategy interface.
package filters
import (
"fmt"
"slices"
"strings"
"github.com/agnivade/levenshtein"
"github.com/defenseunicorns/pkg/helpers"
"github.com/racer159/jackal/src/pkg/interactive"
"github.com/racer159/jackal/src/types"
)
// ForDeploy creates a new deployment filter.
func ForDeploy(optionalComponents string, isInteractive bool) ComponentFilterStrategy {
requested := helpers.StringToSlice(optionalComponents)
return &deploymentFilter{
requested,
isInteractive,
}
}
// deploymentFilter is the default filter for deployments.
type deploymentFilter struct {
requestedComponents []string
isInteractive bool
}
// Errors for the deployment filter.
var (
ErrMultipleSameGroup = fmt.Errorf("cannot specify multiple components from the same group")
ErrNoDefaultOrSelection = fmt.Errorf("no default or selected component found")
ErrNotFound = fmt.Errorf("no compatible components found")
ErrSelectionCanceled = fmt.Errorf("selection canceled")
)
// Apply applies the filter.
func (f *deploymentFilter) Apply(pkg types.JackalPackage) ([]types.JackalComponent, error) {
var selectedComponents []types.JackalComponent
groupedComponents := map[string][]types.JackalComponent{}
orderedComponentGroups := []string{}
// Group the components by Name and Group while maintaining order
for _, component := range pkg.Components {
groupKey := component.Name
if component.DeprecatedGroup != "" {
groupKey = component.DeprecatedGroup
}
if !slices.Contains(orderedComponentGroups, groupKey) {
orderedComponentGroups = append(orderedComponentGroups, groupKey)
}
groupedComponents[groupKey] = append(groupedComponents[groupKey], component)
}
isPartial := len(f.requestedComponents) > 0 && f.requestedComponents[0] != ""
if isPartial {
matchedRequests := map[string]bool{}
// NOTE: This does not use forIncludedComponents as it takes group, default and required status into account.
for _, groupKey := range orderedComponentGroups {
var groupDefault *types.JackalComponent
var groupSelected *types.JackalComponent
for _, component := range groupedComponents[groupKey] {
// Ensure we have a local version of the component to point to (otherwise the pointer might change on us)
component := component
selectState, matchedRequest := includedOrExcluded(component.Name, f.requestedComponents)
if !isRequired(component) {
if selectState == excluded {
// If the component was explicitly excluded, record the match and continue
matchedRequests[matchedRequest] = true
continue
} else if selectState == unknown && component.Default && groupDefault == nil {
// If the component is default but not included or excluded, remember the default
groupDefault = &component
}
} else {
// Force the selectState to included for Required components
selectState = included
}
if selectState == included {
// If the component was explicitly included, record the match
matchedRequests[matchedRequest] = true
// Then check for already selected groups
if groupSelected != nil {
return nil, fmt.Errorf("%w: group: %s selected: %s, %s", ErrMultipleSameGroup, component.DeprecatedGroup, groupSelected.Name, component.Name)
}
// Then append to the final list
selectedComponents = append(selectedComponents, component)
groupSelected = &component
}
}
// If nothing was selected from a group, handle the default
if groupSelected == nil && groupDefault != nil {
selectedComponents = append(selectedComponents, *groupDefault)
} else if len(groupedComponents[groupKey]) > 1 && groupSelected == nil && groupDefault == nil {
// If no default component was found, give up
componentNames := []string{}
for _, component := range groupedComponents[groupKey] {
componentNames = append(componentNames, component.Name)
}
return nil, fmt.Errorf("%w: choose from %s", ErrNoDefaultOrSelection, strings.Join(componentNames, ", "))
}
}
// Check that we have matched against all requests
for _, requestedComponent := range f.requestedComponents {
if _, ok := matchedRequests[requestedComponent]; !ok {
closeEnough := []string{}
for _, c := range pkg.Components {
d := levenshtein.ComputeDistance(c.Name, requestedComponent)
if d <= 5 {
closeEnough = append(closeEnough, c.Name)
}
}
return nil, fmt.Errorf("%w: %s, suggestions (%s)", ErrNotFound, requestedComponent, strings.Join(closeEnough, ", "))
}
}
} else {
for _, groupKey := range orderedComponentGroups {
group := groupedComponents[groupKey]
if len(group) > 1 {
if f.isInteractive {
component, err := interactive.SelectChoiceGroup(group)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrSelectionCanceled, err)
}
selectedComponents = append(selectedComponents, component)
} else {
foundDefault := false
componentNames := []string{}
for _, component := range group {
// If the component is default, then use it
if component.Default {
selectedComponents = append(selectedComponents, component)
foundDefault = true
break
}
// Add each component name to the list
componentNames = append(componentNames, component.Name)
}
if !foundDefault {
// If no default component was found, give up
return nil, fmt.Errorf("%w: choose from %s", ErrNoDefaultOrSelection, strings.Join(componentNames, ", "))
}
}
} else {
component := groupedComponents[groupKey][0]
if isRequired(component) {
selectedComponents = append(selectedComponents, component)
continue
}
if f.isInteractive {
selected, err := interactive.SelectOptionalComponent(component)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrSelectionCanceled, err)
}
if selected {
selectedComponents = append(selectedComponents, component)
continue
}
}
if component.Default {
selectedComponents = append(selectedComponents, component)
continue
}
}
}
}
return selectedComponents, nil
}