-
Notifications
You must be signed in to change notification settings - Fork 290
/
params.go
165 lines (143 loc) · 4.89 KB
/
params.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
package execute
import (
"fmt"
"regexp"
"strings"
"github.com/mattn/go-shellwords"
"github.com/spf13/pflag"
)
const (
cantParseCmd = "cannot parse command. Please use 'help' to see supported commands"
incorrectParamFlag = "incorrect use of %s flag: %s"
missingCmdParamValue = `incorrect use of %s flag: an argument is missing. use %s="value" or %s value`
multipleParams = "incorrect use of %s flag: found more than one %s flag"
paramFlagParseErrorMsg = `incorrect use of %s flag: could not parse flag in %s
error: %s
Use %s="value" or %s value`
)
// Flags contains cmd line arguments for executors.
type Flags struct {
CleanCmd string
Filter string
ClusterName string
TokenizedCmd []string
CmdHeader string
}
// ParseFlags parses raw cmd and removes optional params with flags.
func ParseFlags(cmd string) (Flags, error) {
cmd = ensureEvenSingleQuotes(cmd)
cmd, clusterName, err := extractParam(cmd, "cluster-name")
if err != nil {
return Flags{}, err
}
cmd, filter, err := extractParam(cmd, "filter")
if err != nil {
return Flags{}, err
}
// all-clusters flag is the flag used when multiple clusters are connected to the same instance,
// we need to remove it so other commands won't report problems with unknown flag.
cmd, _, err = extractBoolParam(cmd, "all-clusters")
if err != nil {
return Flags{}, fmt.Errorf("while extracting all-cluster flag: %w", err)
}
cmd, cmdHeaderName, err := extractParam(cmd, "bk-cmd-header")
if err != nil {
return Flags{}, err
}
tokenized, err := shellwords.Parse(cmd)
if err != nil {
return Flags{}, err
}
return Flags{
CleanCmd: cmd,
Filter: filter,
ClusterName: clusterName,
TokenizedCmd: tokenized,
CmdHeader: cmdHeaderName,
}, nil
}
func extractParam(cmd, flagName string) (string, string, error) {
flag := fmt.Sprintf("--%s", flagName)
var withParam string
var params []string
args, _ := shellwords.Parse(cmd)
f := pflag.NewFlagSet("extract-params", pflag.ContinueOnError)
f.BoolP("help", "h", false, "to make sure that parsing is ignoring the --help,-h flags")
f.ParseErrorsWhitelist.UnknownFlags = true
f.StringArrayVar(¶ms, flagName, []string{}, "Output filter")
if err := f.Parse(args); err != nil {
return "", "", fmt.Errorf(incorrectParamFlag, flag, err)
}
if len(params) > 1 {
return "", "", fmt.Errorf(multipleParams, flag, flagName)
}
if len(params) == 1 {
withParam = params[0]
if strings.HasPrefix(params[0], "-") {
return "", "", fmt.Errorf(missingCmdParamValue, flag, flag, flag)
}
}
for _, paramVal := range params {
escapedParamVal := regexp.QuoteMeta(paramVal)
paramFlagRegex, err := regexp.Compile(fmt.Sprintf(`%s[=|(' ')]*('%s'|"%s"|%s)("|')*`,
flag,
escapedParamVal,
escapedParamVal,
escapedParamVal))
if err != nil {
return "", "", fmt.Errorf("could not extract provided %s", flagName)
}
matches := paramFlagRegex.FindStringSubmatch(cmd)
if len(matches) == 0 {
return "", "", fmt.Errorf(paramFlagParseErrorMsg, flag, cmd, "it contains unsupported characters.", flag, flag)
}
cmd = strings.Replace(cmd, fmt.Sprintf(" %s", matches[0]), "", -1)
}
return cmd, withParam, nil
}
// ensureEvenSingleQuotes ensures that single quotes are even. It is required, e.g., for AI plugins to work.
// Otherwise, the command won't be parsed correctly (removing cluster name flags, etc.) and will result in an error.
// This is only a workaround for now. Ultimately, we should find a better and more generic way for extracting
// parameters. Additionally, we should delegate command tokenizing to the plugin.
func ensureEvenSingleQuotes(cmd string) string {
for _, k := range []string{`‘`, `'`, `’`} {
no := strings.Count(cmd, k)
if no%2 == 0 {
continue
}
cmd = strings.Replace(cmd, k, k+k, 1)
}
return cmd
}
func extractBoolParam(cmd, flagName string) (string, bool, error) {
flag := fmt.Sprintf("--%s", flagName)
var isSet bool
args, _ := shellwords.Parse(cmd)
f := pflag.NewFlagSet("extract-params", pflag.ContinueOnError)
f.BoolP("help", "h", false, "to make sure that parsing is ignoring the --help,-h flags")
f.ParseErrorsWhitelist.UnknownFlags = true
f.BoolVar(&isSet, flagName, false, "Boolean flag")
if err := f.Parse(args); err != nil {
return "", false, fmt.Errorf(incorrectParamFlag, flag, err)
}
for _, val := range []string{"true", "false"} {
paramFlagRegex, err := regexFlag(flag, val)
if err != nil {
return "", false, fmt.Errorf("could not extract provided %s", flagName)
}
matches := paramFlagRegex.FindStringSubmatch(cmd)
if len(matches) == 0 {
continue
}
cmd = strings.Replace(cmd, fmt.Sprintf(" %s", matches[0]), "", -1)
}
cmd = strings.ReplaceAll(cmd, flag, "")
return cmd, isSet, nil
}
func regexFlag(flag, escapedParamVal string) (*regexp.Regexp, error) {
return regexp.Compile(fmt.Sprintf(`%s[=|(' ')]*('%s'|"%s"|%s)("|')*`,
flag,
escapedParamVal,
escapedParamVal,
escapedParamVal))
}