Skip to content
Open
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
18 changes: 17 additions & 1 deletion add.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ type AddAndCopyOptions struct {
// DirCopyContents copies the directory's contents instead of the
// directory with its contents below it, default true.
DirCopyContents types.OptionalBool
// AllowWildcard controls whether glob patterns are allowed in source
// paths. When false, they are rejected. Defaults to true.
AllowWildcard types.OptionalBool
// AllowEmptyWildcard controls whether the operation succeeds when all
// glob patterns match nothing. Defaults to false.
AllowEmptyWildcard types.OptionalBool
}

// gitURLFragmentSuffix matches fragments to use as Git reference and build
Expand Down Expand Up @@ -378,7 +384,9 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
var localSourceStats []*copier.StatsForGlob
if len(localSources) > 0 {
statOptions := copier.StatOptions{
CheckForArchives: extract,
CheckForArchives: extract,
DisallowWildcard: options.AllowWildcard == types.OptionalBoolFalse,
AllowEmptyWildcard: options.AllowEmptyWildcard == types.OptionalBoolTrue,
}
localSourceStats, err = copier.Stat(contextDir, contextDir, statOptions, localSources)
if err != nil {
Expand All @@ -399,11 +407,17 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
return fmt.Errorf("checking on sources under %q: %v", contextDir, errorText)
}
if len(localSourceStat.Globbed) == 0 {
if options.AllowEmptyWildcard == types.OptionalBoolTrue {
continue
}
return fmt.Errorf("checking source under %q: no glob matches: %w", contextDir, syscall.ENOENT)
}
numLocalSourceItems += len(localSourceStat.Globbed)
}
if numLocalSourceItems+len(remoteSources)+len(gitSources) == 0 {
if options.AllowEmptyWildcard == types.OptionalBoolTrue {
return nil
}
return fmt.Errorf("no sources %v found: %w", sources, syscall.ENOENT)
}

Expand Down Expand Up @@ -795,6 +809,8 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
StripStickyBit: options.StripStickyBit,
Parents: options.Parents,
Timestamp: options.Timestamp,
DisallowWildcard: options.AllowWildcard == types.OptionalBoolFalse,
AllowEmptyWildcard: options.AllowEmptyWildcard == types.OptionalBoolTrue,
}
getErr = copier.Get(contextDir, contextDir, getOptions, []string{globbedToGlobbable(globbed)}, writer)
closeErr = writer.Close()
Expand Down
59 changes: 37 additions & 22 deletions cmd/buildah/addcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,35 @@ import (
"go.podman.io/buildah/pkg/cli"
"go.podman.io/buildah/pkg/parse"
"go.podman.io/common/pkg/auth"
"go.podman.io/image/v5/types"
"go.podman.io/storage"
)

type addCopyResults struct {
addHistory bool
chmod string
chown string
checksum string
quiet bool
ignoreFile string
contextdir string
from string
blobCache string
decryptionKeys []string
removeSignatures bool
signaturePolicy string
authfile string
creds string
tlsVerify bool
certDir string
retry int
retryDelay string
excludes []string
parents bool
timestamp string
link bool
addHistory bool
chmod string
chown string
checksum string
quiet bool
ignoreFile string
contextdir string
from string
blobCache string
decryptionKeys []string
removeSignatures bool
signaturePolicy string
authfile string
creds string
tlsVerify bool
certDir string
retry int
retryDelay string
excludes []string
parents bool
timestamp string
link bool
allowWildcard bool
allowEmptyWildcard bool
}

func createCommand(addCopy string, desc string, short string, opts *addCopyResults) *cobra.Command {
Expand Down Expand Up @@ -101,6 +104,8 @@ func applyFlagVars(flags *pflag.FlagSet, opts *addCopyResults) {
panic(fmt.Sprintf("error marking signature-policy as hidden: %v", err))
}
flags.StringVar(&opts.timestamp, "timestamp", "", "set timestamps on new content to `seconds` after the epoch")
flags.BoolVar(&opts.allowWildcard, "allow-wildcard", true, "allow glob patterns in source paths")
flags.BoolVar(&opts.allowEmptyWildcard, "allow-empty-wildcard", false, "don't error when glob patterns match nothing")
}

func addcopyInit() {
Expand Down Expand Up @@ -277,6 +282,12 @@ func addAndCopyCmd(c *cobra.Command, args []string, verb string, iopts addCopyRe
}
options.Excludes = append(excludes, options.Excludes...)
}
if c.Flags().Changed("allow-wildcard") {
options.AllowWildcard = types.NewOptionalBool(iopts.allowWildcard)
}
if c.Flags().Changed("allow-empty-wildcard") {
options.AllowEmptyWildcard = types.NewOptionalBool(iopts.allowEmptyWildcard)
}
if iopts.retryDelay != "" {
retryDelay, err := time.ParseDuration(iopts.retryDelay)
if err != nil {
Expand Down Expand Up @@ -307,6 +318,10 @@ func addAndCopyCmd(c *cobra.Command, args []string, verb string, iopts addCopyRe
}

contentType, digest := builder.ContentDigester.Digest()
if digest == "" {
logrus.Debug("no content copied, skipping digest and history")
return nil
}
if !iopts.quiet {
fmt.Printf("%s\n", digest.Hex())
}
Expand Down
56 changes: 41 additions & 15 deletions copier/copier.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,11 @@ func Eval(root string, directory string, _ EvalOptions) (string, error) {

// StatOptions controls parts of Stat()'s behavior.
type StatOptions struct {
UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs when returning results
CheckForArchives bool // check for and populate the IsArchive bit in returned values
Excludes []string // contents to pretend don't exist, using the OS-specific path separator
UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs when returning results
CheckForArchives bool // check for and populate the IsArchive bit in returned values
Excludes []string // contents to pretend don't exist, using the OS-specific path separator
DisallowWildcard bool // reject glob patterns in source paths
AllowEmptyWildcard bool // don't error when glob patterns match nothing
}

// Stat globs the specified pattern in the specified directory and returns its
Expand Down Expand Up @@ -399,6 +401,8 @@ type GetOptions struct {
IgnoreUnreadable bool // ignore errors reading items, instead of returning an error
NoCrossDevice bool // if a subdirectory is a mountpoint with a different device number, include it but skip its contents
Timestamp *time.Time // timestamp to force on all contents
DisallowWildcard bool // reject glob patterns in source paths
AllowEmptyWildcard bool // don't error when glob patterns match nothing
}

// Get produces an archive containing items that match the specified glob
Expand All @@ -420,7 +424,9 @@ func Get(root string, directory string, options GetOptions, globs []string, bulk
Directory: directory,
Globs: slices.Clone(globs),
StatOptions: StatOptions{
CheckForArchives: options.ExpandArchives,
CheckForArchives: options.ExpandArchives,
DisallowWildcard: options.DisallowWildcard,
AllowEmptyWildcard: options.AllowEmptyWildcard,
},
GetOptions: options,
}
Expand Down Expand Up @@ -1108,6 +1114,10 @@ func copierHandlerEval(req request) *response {
return &response{Eval: evalResponse{Evaluated: filepath.Join(req.rootPrefix, resolvedTarget)}}
}

func containsWildcards(path string) bool {
return strings.ContainsAny(path, "*?[")
}

func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) *response {
errorResponse := func(fmtspec string, args ...any) *response {
return &response{Error: fmt.Sprintf(fmtspec, args...), Stat: statResponse{}}
Expand All @@ -1120,13 +1130,17 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *id
s := StatsForGlob{
Glob: req.preservedGlobs[i],
}
// glob this pattern
hasWildcards := containsWildcards(glob)
if req.StatOptions.DisallowWildcard && hasWildcards {
s.Error = fmt.Sprintf("copier: stat: %q: wildcards are not allowed", glob)
stats = append(stats, &s)
continue
}
globMatched, err := extendedGlob(glob)
if err != nil {
s.Error = fmt.Sprintf("copier: stat: %q while matching glob pattern %q", err.Error(), glob)
}

if len(globMatched) == 0 && strings.ContainsAny(glob, "*?[") {
if len(globMatched) == 0 && hasWildcards {
continue
}
// collect the matches
Expand Down Expand Up @@ -1218,18 +1232,18 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *id
result.IsArchive = isArchivePath(globbed)
}
}
// no unskipped matches -> error
if len(s.Globbed) == 0 {
s.Globbed = nil
s.Results = nil
s.Error = fmt.Sprintf("copier: stat: %q: %v", glob, syscall.ENOENT)
if !hasWildcards || !req.StatOptions.AllowEmptyWildcard {
s.Error = fmt.Sprintf("copier: stat: %q: %v", glob, syscall.ENOENT)
}
}
stats = append(stats, &s)
}
// no matches -> error
if len(stats) == 0 {
if len(stats) == 0 && !req.StatOptions.AllowEmptyWildcard {
s := StatsForGlob{
Error: fmt.Sprintf("copier: stat: %q: %v", req.Globs, syscall.ENOENT),
Error: fmt.Sprintf("copier: stat: globs %v matched nothing", req.Globs),
}
stats = append(stats, &s)
}
Expand Down Expand Up @@ -1307,10 +1321,20 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
var queue []queueItem
globMatchedCount := 0
for _, glob := range req.Globs {
hasWildcards := containsWildcards(glob)
if req.GetOptions.DisallowWildcard && hasWildcards {
return errorResponse("copier: get: %q: wildcards are not allowed", glob)
}
globMatched, err := extendedGlob(glob)
if err != nil {
return errorResponse("copier: get: glob %q: %v", glob, err)
}
if len(globMatched) == 0 {
if hasWildcards {
continue
}
return errorResponse("copier: get: %q: %v", glob, syscall.ENOENT)
}
for _, path := range globMatched {
var parents []string
if req.GetOptions.Parents {
Expand All @@ -1320,9 +1344,11 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
queue = append(queue, queueItem{glob: path, parents: parents})
}
}
// no matches -> error
if len(queue) == 0 {
return errorResponse("copier: get: globs %v matched nothing (%d filtered out): %v", req.Globs, globMatchedCount, syscall.ENOENT)
if req.GetOptions.AllowEmptyWildcard {
return &response{Stat: statResponse.Stat, Get: getResponse{}}, nil, nil
}
return errorResponse("copier: get: globs %v matched nothing (%d filtered out)", req.Globs, globMatchedCount)
}
topInfo, err := os.Stat(req.Directory)
if err != nil {
Expand Down Expand Up @@ -1555,7 +1581,7 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
itemsCopied++
}
}
if itemsCopied == 0 {
if itemsCopied == 0 && !req.GetOptions.AllowEmptyWildcard {
return fmt.Errorf("copier: get: copied no items: %w", syscall.ENOENT)
}
return nil
Expand Down
Loading
Loading