From 2351f5b9153ae6e79db14949ccdee825f23f0b2f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 2 Sep 2025 12:15:45 +0200 Subject: [PATCH] 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 --- cli-plugins/manager/manager.go | 2 +- cli-plugins/manager/plugin.go | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index 6bb047f17..009ea1795 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -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) diff --git a/cli-plugins/manager/plugin.go b/cli-plugins/manager/plugin.go index 2caae39b0..40474ed37 100644 --- a/cli-plugins/manager/plugin.go +++ b/cli-plugins/manager/plugin.go @@ -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 +}