mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-10 13:37:08 +08:00
history: add filters to ls
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -14,10 +15,12 @@ import (
|
|||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/desktop"
|
"github.com/docker/buildx/util/desktop"
|
||||||
|
"github.com/docker/buildx/util/gitutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,6 +41,9 @@ type lsOptions struct {
|
|||||||
builder string
|
builder string
|
||||||
format string
|
format string
|
||||||
noTrunc bool
|
noTrunc bool
|
||||||
|
|
||||||
|
filters []string
|
||||||
|
local bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLs(ctx context.Context, dockerCli command.Cli, opts lsOptions) error {
|
func runLs(ctx context.Context, dockerCli command.Cli, opts lsOptions) error {
|
||||||
@ -56,7 +62,29 @@ func runLs(ctx context.Context, dockerCli command.Cli, opts lsOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := queryRecords(ctx, "", nodes, nil)
|
queryOptions := &queryOptions{}
|
||||||
|
|
||||||
|
if opts.local {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
||||||
|
if err != nil {
|
||||||
|
if st, err1 := os.Stat(path.Join(wd, ".git")); err1 == nil && st.IsDir() {
|
||||||
|
return errors.Wrap(err, "git was not found in the system")
|
||||||
|
}
|
||||||
|
return errors.Wrapf(err, "could not find git repository for local filter")
|
||||||
|
}
|
||||||
|
remote, err := gitc.RemoteURL()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not get remote URL for local filter")
|
||||||
|
}
|
||||||
|
queryOptions.Filters = append(queryOptions.Filters, fmt.Sprintf("repository=%s", remote))
|
||||||
|
}
|
||||||
|
queryOptions.Filters = append(queryOptions.Filters, opts.filters...)
|
||||||
|
|
||||||
|
out, err := queryRecords(ctx, "", nodes, queryOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -92,6 +120,8 @@ func lsCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
|||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
||||||
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
|
flags.StringArrayVar(&options.filters, "filter", nil, `Provide filter values (e.g., "status=error")`)
|
||||||
|
flags.BoolVar(&options.local, "local", false, "List records for current repository only")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package history
|
package history
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -19,6 +21,8 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const recordsLimit = 50
|
||||||
|
|
||||||
func buildName(fattrs map[string]string, ls *localstate.State) string {
|
func buildName(fattrs map[string]string, ls *localstate.State) string {
|
||||||
var res string
|
var res string
|
||||||
|
|
||||||
@ -110,6 +114,7 @@ type historyRecord struct {
|
|||||||
|
|
||||||
type queryOptions struct {
|
type queryOptions struct {
|
||||||
CompletedOnly bool
|
CompletedOnly bool
|
||||||
|
Filters []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryRecords(ctx context.Context, ref string, nodes []builder.Node, opts *queryOptions) ([]historyRecord, error) {
|
func queryRecords(ctx context.Context, ref string, nodes []builder.Node, opts *queryOptions) ([]historyRecord, error) {
|
||||||
@ -126,6 +131,11 @@ func queryRecords(ctx context.Context, ref string, nodes []builder.Node, opts *q
|
|||||||
ref = ""
|
ref = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filters []string
|
||||||
|
if opts != nil {
|
||||||
|
filters = opts.Filters
|
||||||
|
}
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
node := node
|
node := node
|
||||||
@ -138,9 +148,24 @@ func queryRecords(ctx context.Context, ref string, nodes []builder.Node, opts *q
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(filters) > 0 {
|
||||||
|
filters, err = dockerFiltersToBuildkit(filters)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sb := bytes.NewBuffer(nil)
|
||||||
|
w := csv.NewWriter(sb)
|
||||||
|
w.Write(filters)
|
||||||
|
w.Flush()
|
||||||
|
filters = []string{strings.TrimSuffix(sb.String(), "\n")}
|
||||||
|
}
|
||||||
|
|
||||||
serv, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
serv, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
||||||
EarlyExit: true,
|
EarlyExit: true,
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
|
Limit: recordsLimit,
|
||||||
|
Filter: filters,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -219,3 +244,41 @@ func formatDuration(d time.Duration) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%dm %2ds", int(d.Minutes()), int(d.Seconds())%60)
|
return fmt.Sprintf("%dm %2ds", int(d.Minutes()), int(d.Seconds())%60)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dockerFiltersToBuildkit(in []string) ([]string, error) {
|
||||||
|
out := []string{}
|
||||||
|
for _, f := range in {
|
||||||
|
key, value, sep, found := multiCut(f, "=", "!=", "<=", "<", ">=", ">")
|
||||||
|
if !found {
|
||||||
|
return 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)
|
||||||
|
}
|
||||||
|
if sep == "=" {
|
||||||
|
if key == "status" {
|
||||||
|
sep = "=="
|
||||||
|
} else {
|
||||||
|
sep = "~="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "createdAt", "completedAt", "duration":
|
||||||
|
if sep == "=" || sep == "!=" {
|
||||||
|
return nil, errors.Errorf("invalid separator for %q, expected <=, <, >= or >", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, key+sep+value)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiCut(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s, "", "", false
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user