mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-17 16:37:46 +08:00

These commands allow working with build records of completed and running builds. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
181 lines
4.2 KiB
Go
181 lines
4.2 KiB
Go
package history
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/docker/buildx/build"
|
|
"github.com/docker/buildx/builder"
|
|
"github.com/docker/buildx/localstate"
|
|
controlapi "github.com/moby/buildkit/api/services/control"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
func buildName(fattrs map[string]string, ls *localstate.State) string {
|
|
var res string
|
|
|
|
var target, contextPath, dockerfilePath, vcsSource string
|
|
if v, ok := fattrs["target"]; ok {
|
|
target = v
|
|
}
|
|
if v, ok := fattrs["context"]; ok {
|
|
contextPath = filepath.ToSlash(v)
|
|
} else if v, ok := fattrs["vcs:localdir:context"]; ok && v != "." {
|
|
contextPath = filepath.ToSlash(v)
|
|
}
|
|
if v, ok := fattrs["vcs:source"]; ok {
|
|
vcsSource = v
|
|
}
|
|
if v, ok := fattrs["filename"]; ok && v != "Dockerfile" {
|
|
dockerfilePath = filepath.ToSlash(v)
|
|
}
|
|
if v, ok := fattrs["vcs:localdir:dockerfile"]; ok && v != "." {
|
|
dockerfilePath = filepath.ToSlash(filepath.Join(v, dockerfilePath))
|
|
}
|
|
|
|
var localPath string
|
|
if ls != nil && !build.IsRemoteURL(ls.LocalPath) {
|
|
if ls.LocalPath != "" && ls.LocalPath != "-" {
|
|
localPath = filepath.ToSlash(ls.LocalPath)
|
|
}
|
|
if ls.DockerfilePath != "" && ls.DockerfilePath != "-" && ls.DockerfilePath != "Dockerfile" {
|
|
dockerfilePath = filepath.ToSlash(ls.DockerfilePath)
|
|
}
|
|
}
|
|
|
|
// remove default dockerfile name
|
|
const defaultFilename = "/Dockerfile"
|
|
hasDefaultFileName := strings.HasSuffix(dockerfilePath, defaultFilename) || dockerfilePath == ""
|
|
dockerfilePath = strings.TrimSuffix(dockerfilePath, defaultFilename)
|
|
|
|
// dockerfile is a subpath of context
|
|
if strings.HasPrefix(dockerfilePath, localPath) && len(dockerfilePath) > len(localPath) {
|
|
res = dockerfilePath[strings.LastIndex(localPath, "/")+1:]
|
|
} else {
|
|
// Otherwise, use basename
|
|
bpath := localPath
|
|
if len(dockerfilePath) > 0 {
|
|
bpath = dockerfilePath
|
|
}
|
|
if len(bpath) > 0 {
|
|
lidx := strings.LastIndex(bpath, "/")
|
|
res = bpath[lidx+1:]
|
|
if !hasDefaultFileName {
|
|
if lidx != -1 {
|
|
res = filepath.ToSlash(filepath.Join(filepath.Base(bpath[:lidx]), res))
|
|
} else {
|
|
res = filepath.ToSlash(filepath.Join(filepath.Base(bpath), res))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(contextPath) > 0 {
|
|
res = contextPath
|
|
}
|
|
if len(target) > 0 {
|
|
if len(res) > 0 {
|
|
res = res + " (" + target + ")"
|
|
} else {
|
|
res = target
|
|
}
|
|
}
|
|
if res == "" && vcsSource != "" {
|
|
return vcsSource
|
|
}
|
|
return res
|
|
}
|
|
|
|
func trimBeginning(s string, n int) string {
|
|
if len(s) <= n {
|
|
return s
|
|
}
|
|
return ".." + s[len(s)-n+2:]
|
|
}
|
|
|
|
type historyRecord struct {
|
|
*controlapi.BuildHistoryRecord
|
|
currentTimestamp *time.Time
|
|
node *builder.Node
|
|
name string
|
|
}
|
|
|
|
func queryRecords(ctx context.Context, ref string, nodes []builder.Node) ([]historyRecord, error) {
|
|
var mu sync.Mutex
|
|
var out []historyRecord
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
for _, node := range nodes {
|
|
node := node
|
|
eg.Go(func() error {
|
|
if node.Driver == nil {
|
|
return nil
|
|
}
|
|
var records []historyRecord
|
|
c, err := node.Driver.Client(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serv, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
|
EarlyExit: true,
|
|
Ref: ref,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
md, err := serv.Header()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var ts *time.Time
|
|
if v, ok := md[headerKeyTimestamp]; ok {
|
|
t, err := time.Parse(time.RFC3339Nano, v[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ts = &t
|
|
}
|
|
defer serv.CloseSend()
|
|
for {
|
|
he, err := serv.Recv()
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
if he.Type == controlapi.BuildHistoryEventType_DELETED || he.Record == nil {
|
|
continue
|
|
}
|
|
records = append(records, historyRecord{
|
|
BuildHistoryRecord: he.Record,
|
|
currentTimestamp: ts,
|
|
node: &node,
|
|
})
|
|
}
|
|
mu.Lock()
|
|
out = append(out, records...)
|
|
mu.Unlock()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
if err := eg.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func formatDuration(d time.Duration) string {
|
|
if d < time.Minute {
|
|
return fmt.Sprintf("%.1fs", d.Seconds())
|
|
}
|
|
return fmt.Sprintf("%dm %2ds", int(d.Minutes()), int(d.Seconds())%60)
|
|
}
|