Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ linters:
- dupword # Detects duplicate words.
- durationcheck
- errchkjson
- forbidigo
- gocritic # Metalinter; detects bugs, performance, and styling issues.
- gocyclo
- gofumpt # Detects whether code was gofumpt-ed.
Expand Down Expand Up @@ -66,6 +67,11 @@ linters-settings:
desc: Use github.com/google/uuid instead.
- pkg: "io/ioutil"
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
forbidigo:
forbid:
- pkg: ^regexp$
p: ^regexp\.MustCompile
msg: Use internal/lazyregexp.New instead.
gocyclo:
min-complexity: 16
gosec:
Expand Down
4 changes: 2 additions & 2 deletions cli-plugins/manager/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/internal/lazyregexp"
"github.com/spf13/cobra"
)

var pluginNameRe = regexp.MustCompile("^[a-z][a-z0-9]*$")
var pluginNameRe = lazyregexp.New("^[a-z][a-z0-9]*$")

// Plugin represents a potential plugin with all it's metadata.
type Plugin struct {
Expand Down
4 changes: 2 additions & 2 deletions cli/command/container/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
"path"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"

"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
Expand All @@ -40,7 +40,7 @@ const (
seccompProfileUnconfined = "unconfined"
)

var deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
var deviceCgroupRuleRegexp = lazyregexp.New(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)

// containerOptions is a data object with all the options for creating a container
type containerOptions struct {
Expand Down
4 changes: 2 additions & 2 deletions cli/command/image/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"io"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"

Expand All @@ -23,6 +22,7 @@ import (
"github.com/docker/cli/cli/internal/jsonstream"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
Expand Down Expand Up @@ -432,7 +432,7 @@ func validateTag(rawRepo string) (string, error) {
return rawRepo, nil
}

var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
var dockerfileFromLinePattern = lazyregexp.New(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)

// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
Expand Down
4 changes: 2 additions & 2 deletions cli/command/system/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io"
"regexp"
"sort"
"strings"

Expand All @@ -19,6 +18,7 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/debug"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/cli/templates"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
Expand Down Expand Up @@ -142,7 +142,7 @@ func addServerInfo(ctx context.Context, dockerCli command.Cli, format string, in

// placeHolders does a rudimentary match for possible placeholders in a
// template, matching a '.', followed by an letter (a-z/A-Z).
var placeHolders = regexp.MustCompile(`\.[a-zA-Z]`)
var placeHolders = lazyregexp.New(`\.[a-zA-Z]`)

// needsServerInfo detects if the given template uses any server information.
// If only client-side information is used in the template, we can skip
Expand Down
4 changes: 2 additions & 2 deletions cli/command/trust/key_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/lazyregexp"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/theupdateframework/notary"
Expand Down Expand Up @@ -41,7 +41,7 @@ func newKeyGenerateCommand(dockerCli command.Streams) *cobra.Command {
}

// key names can use lowercase alphanumeric + _ + - characters
var validKeyName = regexp.MustCompile(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
var validKeyName = lazyregexp.New(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString

// validate that all of the key names are unique and are alphanumeric + _ + -
// and that we do not already have public key files in the target dir on disk
Expand Down
4 changes: 2 additions & 2 deletions cli/command/trust/signer_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import (
"io"
"os"
"path"
"regexp"
"strings"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/cli/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -45,7 +45,7 @@ func newSignerAddCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}

var validSignerName = regexp.MustCompile(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
var validSignerName = lazyregexp.New(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString

func addSigner(ctx context.Context, dockerCLI command.Cli, options signerAddOptions) error {
signerName := options.signer
Expand Down
46 changes: 34 additions & 12 deletions cli/compose/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,30 @@ import (
"fmt"
"regexp"
"strings"

"github.com/docker/cli/internal/lazyregexp"
)

const (
delimiter = "\\$"
subst = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?"
)

var defaultPattern = regexp.MustCompile(fmt.Sprintf(
var defaultPattern = lazyregexp.New(fmt.Sprintf(
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
delimiter, delimiter, subst, subst,
))

// regexper is an internal interface to allow passing a [lazyregexp.Regexp]
// in places where a custom ("regular") [regexp.Regexp] is accepted. It defines
// only the methods we currently use.
type regexper interface {
FindAllStringSubmatch(s string, n int) [][]string
FindStringSubmatch(s string) []string
ReplaceAllStringFunc(src string, repl func(string) string) string
SubexpNames() []string
}

// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
var DefaultSubstituteFuncs = []SubstituteFunc{
softDefault,
Expand Down Expand Up @@ -51,10 +63,16 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
// SubstituteWith substitutes variables in the string with their values.
// It accepts additional substitute function.
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
return substituteWith(template, mapping, pattern, subsFuncs...)
}

// SubstituteWith substitutes variables in the string with their values.
// It accepts additional substitute function.
func substituteWith(template string, mapping Mapping, pattern regexper, subsFuncs ...SubstituteFunc) (string, error) {
var err error
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
matches := pattern.FindStringSubmatch(substring)
groups := matchGroups(matches, pattern)
groups := matchGroups(matches, defaultPattern)
if escaped := groups["escaped"]; escaped != "" {
return escaped
}
Expand Down Expand Up @@ -93,38 +111,42 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su

// Substitute variables in the string with their values
func Substitute(template string, mapping Mapping) (string, error) {
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
return substituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
}

// ExtractVariables returns a map of all the variables defined in the specified
// composefile (dict representation) and their default value if any.
func ExtractVariables(configDict map[string]any, pattern *regexp.Regexp) map[string]string {
return extractVariables(configDict, pattern)
}

func extractVariables(configDict map[string]any, pattern regexper) map[string]string {
if pattern == nil {
pattern = defaultPattern
}
return recurseExtract(configDict, pattern)
}

func recurseExtract(value any, pattern *regexp.Regexp) map[string]string {
func recurseExtract(value any, pattern regexper) map[string]string {
m := map[string]string{}

switch value := value.(type) {
switch val := value.(type) {
case string:
if values, is := extractVariable(value, pattern); is {
if values, is := extractVariable(val, pattern); is {
for _, v := range values {
m[v.name] = v.value
}
}
case map[string]any:
for _, elem := range value {
for _, elem := range val {
submap := recurseExtract(elem, pattern)
for key, value := range submap {
m[key] = value
for k, v := range submap {
m[k] = v
}
}

case []any:
for _, elem := range value {
for _, elem := range val {
if values, is := extractVariable(elem, pattern); is {
for _, v := range values {
m[v.name] = v.value
Expand All @@ -141,7 +163,7 @@ type extractedValue struct {
value string
}

func extractVariable(value any, pattern *regexp.Regexp) ([]extractedValue, bool) {
func extractVariable(value any, pattern regexper) ([]extractedValue, bool) {
sValue, ok := value.(string)
if !ok {
return []extractedValue{}, false
Expand Down Expand Up @@ -227,7 +249,7 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s
return value, true, nil
}

func matchGroups(matches []string, pattern *regexp.Regexp) map[string]string {
func matchGroups(matches []string, pattern regexper) map[string]string {
groups := make(map[string]string)
for i, name := range pattern.SubexpNames()[1:] {
groups[name] = matches[i+1]
Expand Down
8 changes: 4 additions & 4 deletions cli/compose/template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,15 +169,15 @@ func TestSubstituteWithCustomFunc(t *testing.T) {
return value, true, nil
}

result, err := SubstituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
result, err := substituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok first", result))

result, err = SubstituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
result, err = substituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok ", result))

_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
_, err = substituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
assert.Check(t, is.ErrorContains(err, "required variable"))
}

Expand Down Expand Up @@ -278,7 +278,7 @@ func TestExtractVariables(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := ExtractVariables(tc.dict, defaultPattern)
actual := extractVariables(tc.dict, defaultPattern)
assert.Check(t, is.DeepEqual(actual, tc.expected))
})
}
Expand Down
4 changes: 2 additions & 2 deletions cli/context/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ import (
"net/http"
"path"
"path/filepath"
"regexp"
"strings"

"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/docker/errdefs"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)

const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$"

var restrictedNameRegEx = regexp.MustCompile(restrictedNamePattern)
var restrictedNameRegEx = lazyregexp.New(restrictedNamePattern)

// Store provides a context store for easily remembering endpoints configuration
type Store interface {
Expand Down
3 changes: 2 additions & 1 deletion e2e/cli-plugins/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func TestGlobalHelp(t *testing.T) {
`Invalid Plugins:`,
`\s+badmeta\s+invalid metadata: invalid character 'i' looking for beginning of object key string`,
} {
expected := regexp.MustCompile(`(?m)^` + s + `$`)
expected, err := regexp.Compile(`(?m)^` + s + `$`)
assert.NilError(t, err)
matches := expected.FindAllString(output, -1)
assert.Equal(t, len(matches), 1, "Did not find expected number of matches for %q in `docker help` output", expected)
}
Expand Down
Loading
Loading