mirror of
https://github.com/moby/buildkit.git
synced 2026-08-13 17:07:00 +00:00
A CONVERT rule whose selector uses matchType EXACT matched the source but silently performed no conversion: the source identifier was left unchanged and no error was returned. The destination of a CONVERT is computed by selectorCache.Format(match, format), where format is the rule's Updates.Identifier. For WILDCARD and REGEX the groups captured from match are substituted into format. The EXACT branch has no captures and should return the target format verbatim, but it returned s.Identifier (the selector's own identifier, i.e. the matched source) instead. mutate() then computed a destination equal to the source, saw op.Identifier == dest, and returned mutated=false without applying the update. This made exact-match source pinning/substitution silently fail, e.g. pinning an image tag to a digest for reproducible builds. Return format from the EXACT branch. The empty-destination case is unaffected: mutate() already falls back to the selector identifier before calling Format, so an empty Updates.Identifier remains a correct no-op. Add testConvertExact covering an explicit MatchType_EXACT conversion; the existing testConvert only exercised the default wildcard path. Signed-off-by: ZRHann <zrhann@foxmail.com>
87 lines
2.2 KiB
Go
87 lines
2.2 KiB
Go
package sourcepolicy
|
|
|
|
import (
|
|
"regexp"
|
|
"sync"
|
|
|
|
spb "github.com/moby/buildkit/sourcepolicy/pb"
|
|
"github.com/moby/buildkit/util/wildcard"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// selectorCache wraps a protobuf selector in order to store cached state such as the compiled regexes.
|
|
type selectorCache struct {
|
|
*spb.Selector
|
|
regex func() (*regexp.Regexp, error)
|
|
wildcard func() (*wildcardCache, error)
|
|
}
|
|
|
|
func newSelectorCache(sel *spb.Selector) *selectorCache {
|
|
s := &selectorCache{Selector: sel}
|
|
s.regex = sync.OnceValues(func() (*regexp.Regexp, error) {
|
|
return regexp.Compile(sel.Identifier)
|
|
})
|
|
s.wildcard = sync.OnceValues(func() (*wildcardCache, error) {
|
|
w, err := wildcard.New(sel.Identifier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &wildcardCache{w: w}, nil
|
|
})
|
|
return s
|
|
}
|
|
|
|
// Format formats the provided ref according to the match/type of the source.
|
|
//
|
|
// For example, if the source is a wildcard, the ref will be formatted with the wildcard in the source replacing the parameters in the destination.
|
|
//
|
|
// matcher: wildcard source: "docker.io/library/golang:*" match: "docker.io/library/golang:1.19" format: "docker.io/library/golang:${1}-alpine" result: "docker.io/library/golang:1.19-alpine"
|
|
func (s *selectorCache) Format(match, format string) (string, error) {
|
|
switch s.MatchType {
|
|
case spb.MatchType_EXACT:
|
|
return format, nil
|
|
case spb.MatchType_REGEX:
|
|
re, err := s.regex()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return re.ReplaceAllString(match, format), nil
|
|
case spb.MatchType_WILDCARD:
|
|
w, err := s.wildcard()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
m := w.Match(match)
|
|
if m == nil {
|
|
return match, nil
|
|
}
|
|
return m.Format(format)
|
|
}
|
|
return "", errors.Errorf("unknown match type: %s", s.MatchType)
|
|
}
|
|
|
|
// wildcardCache wraps a wildcard.Wildcard to cache returned matches by ref.
|
|
// This way a match only needs to be computed once per ref.
|
|
type wildcardCache struct {
|
|
mu sync.Mutex
|
|
w *wildcard.Wildcard
|
|
m map[string]*wildcard.Match
|
|
}
|
|
|
|
func (w *wildcardCache) Match(ref string) *wildcard.Match {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
if w.m == nil {
|
|
w.m = make(map[string]*wildcard.Match)
|
|
}
|
|
|
|
if m, ok := w.m[ref]; ok {
|
|
return m
|
|
}
|
|
|
|
m := w.w.Match(ref)
|
|
w.m[ref] = m
|
|
return m
|
|
}
|