cli-plugins/manager: replace pluginNameRe for isValidPluginName utility

The pluginNameRe was a basic regular expression, effectively only checking
if the name consisted of lowercase alphanumeric characters. Replace it
with a minimal utility to do the same, without having to use regular
expressions (or the "lazyregexp" package).

Some quick benchmarking (not committed) show that the non-regex approach
is ~25x faster:

    BenchmarkIsValidPluginName_Regex_Valid-10       13956240        81.39  ns/op       0 B/op        0 allocs/op
    BenchmarkIsValidPluginName_Manual_Valid-10     360003060         3.318 ns/op       0 B/op        0 allocs/op

    BenchmarkIsValidPluginName_Regex_Invalid-10     35281794        33.74  ns/op       0 B/op        0 allocs/op
    BenchmarkIsValidPluginName_Manual_Invalid-10   906072663         1.320 ns/op       0 B/op        0 allocs/op

    BenchmarkIsValidPluginName_Regex_Parallel-10    96595677        12.04  ns/op       0 B/op        0 allocs/op
    BenchmarkIsValidPluginName_Manual_Parallel-10  1000000000        0.4541 ns/op      0 B/op        0 allocs/op

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-09-02 12:15:45 +02:00
parent 153bd95158
commit 2351f5b915
2 changed files with 26 additions and 6 deletions

View File

@ -169,7 +169,7 @@ func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Com
// have been provided by cobra to our caller. This is because
// they lack e.g. global options which we must propagate here.
args := os.Args[1:]
if !pluginNameRe.MatchString(name) {
if !isValidPluginName(name) {
// We treat this as "not found" so that callers will
// fallback to their "invalid" command path.
return nil, errPluginNotFound(name)

View File

@ -12,12 +12,9 @@ import (
"strings"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/internal/lazyregexp"
"github.com/spf13/cobra"
)
var pluginNameRe = lazyregexp.New("^[a-z][a-z0-9]*$")
// Plugin represents a potential plugin with all it's metadata.
type Plugin struct {
metadata.Metadata
@ -85,8 +82,8 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
}
// Now apply the candidate tests, so these update p.Err.
if !pluginNameRe.MatchString(p.Name) {
p.Err = newPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String())
if !isValidPluginName(p.Name) {
p.Err = newPluginError("plugin candidate %q did not match %q", p.Name, pluginNameFormat)
return p, nil
}
@ -147,3 +144,26 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
return hookCmdOutput, nil
}
// pluginNameFormat is used as part of errors for invalid plugin-names.
// We should consider making this less technical ("must start with "a-z",
// and only consist of lowercase alphanumeric characters").
const pluginNameFormat = `^[a-z][a-z0-9]*$`
func isValidPluginName(s string) bool {
if len(s) == 0 {
return false
}
// first character must be a-z
if c := s[0]; c < 'a' || c > 'z' {
return false
}
// followed by a-z or 0-9
for i := 1; i < len(s); i++ {
c := s[i]
if (c < 'a' || c > 'z') && (c < '0' || c > '9') {
return false
}
}
return true
}