mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 00:47:48 +08:00
history: add local filters for older buildkit versions
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
900502b139
commit
f1b895196c
@ -17,6 +17,7 @@ import (
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/localstate"
|
||||
controlapi "github.com/moby/buildkit/api/services/control"
|
||||
"github.com/moby/buildkit/util/gitutil"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@ -149,8 +150,9 @@ func queryRecords(ctx context.Context, ref string, nodes []builder.Node, opts *q
|
||||
return err
|
||||
}
|
||||
|
||||
var matchers []matchFunc
|
||||
if len(filters) > 0 {
|
||||
filters, err = dockerFiltersToBuildkit(filters)
|
||||
filters, matchers, err = dockerFiltersToBuildkit(filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -183,6 +185,7 @@ func queryRecords(ctx context.Context, ref string, nodes []builder.Node, opts *q
|
||||
ts = &t
|
||||
}
|
||||
defer serv.CloseSend()
|
||||
loop0:
|
||||
for {
|
||||
he, err := serv.Recv()
|
||||
if err != nil {
|
||||
@ -198,6 +201,13 @@ func queryRecords(ctx context.Context, ref string, nodes []builder.Node, opts *q
|
||||
continue
|
||||
}
|
||||
|
||||
// for older buildkit that don't support filters apply local filters
|
||||
for _, matcher := range matchers {
|
||||
if !matcher(he.Record) {
|
||||
continue loop0
|
||||
}
|
||||
}
|
||||
|
||||
records = append(records, historyRecord{
|
||||
BuildHistoryRecord: he.Record,
|
||||
currentTimestamp: ts,
|
||||
@ -245,18 +255,22 @@ func formatDuration(d time.Duration) string {
|
||||
return fmt.Sprintf("%dm %2ds", int(d.Minutes()), int(d.Seconds())%60)
|
||||
}
|
||||
|
||||
func dockerFiltersToBuildkit(in []string) ([]string, error) {
|
||||
type matchFunc func(*controlapi.BuildHistoryRecord) bool
|
||||
|
||||
func dockerFiltersToBuildkit(in []string) ([]string, []matchFunc, error) {
|
||||
out := []string{}
|
||||
matchers := []matchFunc{}
|
||||
for _, f := range in {
|
||||
key, value, sep, found := multiCut(f, "=", "!=", "<=", "<", ">=", ">")
|
||||
key, value, sep, found := cutAny(f, "!=", "=", "<=", "<", ">=", ">")
|
||||
if !found {
|
||||
return nil, errors.Errorf("invalid filter %q", f)
|
||||
return nil, nil, errors.Errorf("invalid filter %q", f)
|
||||
}
|
||||
switch key {
|
||||
case "ref", "repository", "status":
|
||||
if sep != "=" && sep != "!=" {
|
||||
return nil, errors.Errorf("invalid separator for %q, expected = or !=", f)
|
||||
return nil, nil, errors.Errorf("invalid separator for %q, expected = or !=", f)
|
||||
}
|
||||
matchers = append(matchers, valueFiler(key, value, sep))
|
||||
if sep == "=" {
|
||||
if key == "status" {
|
||||
sep = "=="
|
||||
@ -264,17 +278,122 @@ func dockerFiltersToBuildkit(in []string) ([]string, error) {
|
||||
sep = "~="
|
||||
}
|
||||
}
|
||||
case "createdAt", "completedAt", "duration":
|
||||
case "startedAt", "completedAt", "duration":
|
||||
if sep == "=" || sep == "!=" {
|
||||
return nil, errors.Errorf("invalid separator for %q, expected <=, <, >= or >", f)
|
||||
return nil, nil, errors.Errorf("invalid separator for %q, expected <=, <, >= or >", f)
|
||||
}
|
||||
matcher, err := timeBasedFilter(key, value, sep)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
matchers = append(matchers, matcher)
|
||||
default:
|
||||
return nil, nil, errors.Errorf("unsupported filter %q", f)
|
||||
}
|
||||
out = append(out, key+sep+value)
|
||||
}
|
||||
return out, nil
|
||||
return out, matchers, nil
|
||||
}
|
||||
|
||||
func multiCut(s string, seps ...string) (before, after, sep string, found bool) {
|
||||
func valueFiler(key, value, sep string) matchFunc {
|
||||
return func(rec *controlapi.BuildHistoryRecord) bool {
|
||||
var recValue string
|
||||
switch key {
|
||||
case "ref":
|
||||
recValue = rec.Ref
|
||||
case "repository":
|
||||
v, ok := rec.FrontendAttrs["vcs:source"]
|
||||
if ok {
|
||||
recValue = v
|
||||
} else {
|
||||
if context, ok := rec.FrontendAttrs["context"]; ok {
|
||||
if ref, err := gitutil.ParseGitRef(context); err == nil {
|
||||
recValue = ref.Remote
|
||||
}
|
||||
}
|
||||
}
|
||||
case "status":
|
||||
if rec.CompletedAt != nil {
|
||||
if rec.Error != nil {
|
||||
if strings.Contains(rec.Error.Message, "context canceled") {
|
||||
recValue = "canceled"
|
||||
} else {
|
||||
recValue = "error"
|
||||
}
|
||||
} else {
|
||||
recValue = "completed"
|
||||
}
|
||||
} else {
|
||||
recValue = "running"
|
||||
}
|
||||
}
|
||||
switch sep {
|
||||
case "=":
|
||||
if key == "status" {
|
||||
return recValue == value
|
||||
}
|
||||
return strings.Contains(recValue, value)
|
||||
case "!=":
|
||||
return recValue != value
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func timeBasedFilter(key, value, sep string) (matchFunc, error) {
|
||||
var cmp int64
|
||||
switch key {
|
||||
case "startedAt", "completedAt":
|
||||
v, err := time.ParseDuration(value)
|
||||
if err == nil {
|
||||
tm := time.Now().Add(-v)
|
||||
cmp = tm.Unix()
|
||||
} else {
|
||||
tm, err := time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("invalid time %s", value)
|
||||
}
|
||||
cmp = tm.Unix()
|
||||
}
|
||||
case "duration":
|
||||
v, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("invalid duration %s", value)
|
||||
}
|
||||
cmp = int64(v)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return func(rec *controlapi.BuildHistoryRecord) bool {
|
||||
var val int64
|
||||
switch key {
|
||||
case "startedAt":
|
||||
val = rec.CreatedAt.AsTime().Unix()
|
||||
case "completedAt":
|
||||
if rec.CompletedAt != nil {
|
||||
val = rec.CompletedAt.AsTime().Unix()
|
||||
}
|
||||
case "duration":
|
||||
if rec.CompletedAt != nil {
|
||||
val = int64(rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime()))
|
||||
}
|
||||
}
|
||||
switch sep {
|
||||
case ">=":
|
||||
return val >= cmp
|
||||
case "<=":
|
||||
return val <= cmp
|
||||
case ">":
|
||||
return val > cmp
|
||||
default:
|
||||
return val < cmp
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func cutAny(s string, seps ...string) (before, after, sep string, found bool) {
|
||||
for _, sep := range seps {
|
||||
if idx := strings.Index(s, sep); idx != -1 {
|
||||
return s[:idx], s[idx+len(sep):], sep, true
|
||||
|
@ -6,10 +6,12 @@ List build records
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:-----------------------------------------|
|
||||
|:----------------|:--------------|:--------|:---------------------------------------------|
|
||||
| `--builder` | `string` | | Override the configured builder instance |
|
||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||
| `--filter` | `stringArray` | | Provide filter values (e.g., `status=error`) |
|
||||
| `--format` | `string` | `table` | Format the output |
|
||||
| `--local` | `bool` | | List records for current repository only |
|
||||
| `--no-trunc` | `bool` | | Don't truncate output |
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user