mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 09:17:49 +08:00
history: add UI view to traces
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
cfeea34b2d
commit
f9a76355b5
@ -1,29 +1,37 @@
|
|||||||
package history
|
package history
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"net"
|
||||||
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
"github.com/containerd/containerd/v2/core/content/proxy"
|
"github.com/containerd/containerd/v2/core/content/proxy"
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/buildx/util/otelutil"
|
"github.com/docker/buildx/util/otelutil"
|
||||||
|
"github.com/docker/buildx/util/otelutil/jaeger"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
controlapi "github.com/moby/buildkit/api/services/control"
|
controlapi "github.com/moby/buildkit/api/services/control"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/browser"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
jaegerui "github.com/tonistiigi/jaeger-ui-rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type traceOptions struct {
|
type traceOptions struct {
|
||||||
builder string
|
builder string
|
||||||
ref string
|
ref string
|
||||||
containerName string
|
containerName string
|
||||||
|
addr string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTrace(ctx context.Context, dockerCli command.Cli, opts traceOptions) error {
|
func runTrace(ctx context.Context, dockerCli command.Cli, opts traceOptions) error {
|
||||||
@ -104,8 +112,6 @@ func runTrace(ctx context.Context, dockerCli command.Cli, opts traceOptions) err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("trace %+v", rec.Trace)
|
|
||||||
|
|
||||||
c, err := rec.node.Driver.Client(ctx)
|
c, err := rec.node.Driver.Client(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -127,12 +133,68 @@ func runTrace(ctx context.Context, dockerCli command.Cli, opts traceOptions) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: try to upload build to Jaeger UI
|
wrapper := struct {
|
||||||
jd := spans.JaegerData().Data
|
Data []jaeger.Trace `json:"data"`
|
||||||
|
}{
|
||||||
|
Data: spans.JaegerData().Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(wrapper.Data) == 0 {
|
||||||
|
return errors.New("no trace data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !term {
|
||||||
enc := json.NewEncoder(dockerCli.Out())
|
enc := json.NewEncoder(dockerCli.Out())
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
return enc.Encode(jd)
|
return enc.Encode(wrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := jaegerui.NewServer(jaegerui.Config{})
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
if err := enc.Encode(wrapper); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := srv.AddTrace(string(wrapper.Data[0].TraceID), bytes.NewReader(buf.Bytes())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", opts.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "http://" + ln.Addr().String() + "/trace/" + string(wrapper.Data[0].TraceID)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
browser.OpenURL(url)
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Fprintf(dockerCli.Err(), "Trace available at %s\n", url)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
ln.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = srv.Serve(ln)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func traceCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
func traceCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
@ -154,6 +216,7 @@ func traceCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
|||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&options.containerName, "container", "", "Container name")
|
flags.StringVar(&options.containerName, "container", "", "Container name")
|
||||||
|
flags.StringVar(&options.addr, "addr", "127.0.0.1:0", "Address to bind the UI server")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -46,6 +46,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a
|
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6
|
||||||
github.com/zclconf/go-cty v1.16.0
|
github.com/zclconf/go-cty v1.16.0
|
||||||
go.opentelemetry.io/otel v1.31.0
|
go.opentelemetry.io/otel v1.31.0
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0
|
||||||
|
2
go.sum
2
go.sum
@ -447,6 +447,8 @@ github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a h1:EfGw4G0x/8qXW
|
|||||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
|
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6 h1:RT/a0RvdX84iwtOrUK45+wjcNpaG+hS7n7XFYqj4axg=
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6/go.mod h1:3Ez1Paeg+0Ghu3KwpEGC1HgZ4CHDlg+Ez/5Baeomk54=
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
|
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
|
||||||
|
34
vendor/github.com/tonistiigi/jaeger-ui-rest/Dockerfile
generated
vendored
Normal file
34
vendor/github.com/tonistiigi/jaeger-ui-rest/Dockerfile
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
ARG NODE_VERSION=23.6
|
||||||
|
ARG ALPINE_VERSION=3.21
|
||||||
|
ARG GOLANG_VERSION=1.23
|
||||||
|
ARG JAEGERUI_VERSION=v1.66.0
|
||||||
|
|
||||||
|
FROM scratch AS jaegerui-src
|
||||||
|
ARG JAEGERUI_REPO=https://github.com/jaegertracing/jaeger-ui.git
|
||||||
|
ARG JAEGERUI_VERSION
|
||||||
|
ADD ${JAEGERUI_REPO}#${JAEGERUI_VERSION} /
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS builder
|
||||||
|
WORKDIR /work/jaeger-ui
|
||||||
|
COPY --from=jaegerui-src / .
|
||||||
|
RUN npm install
|
||||||
|
WORKDIR /work/jaeger-ui/packages/jaeger-ui
|
||||||
|
RUN NODE_ENVIRONMENT=production npm run build
|
||||||
|
# failed to find a way to avoid legacy build
|
||||||
|
RUN rm build/static/*-legacy* && rm build/static/*.png
|
||||||
|
|
||||||
|
FROM scratch AS jaegerui
|
||||||
|
COPY --from=builder /work/jaeger-ui/packages/jaeger-ui/build /
|
||||||
|
|
||||||
|
FROM alpine AS compressor
|
||||||
|
RUN --mount=target=/in,from=jaegerui <<EOT
|
||||||
|
set -ex
|
||||||
|
mkdir -p /out
|
||||||
|
cp -a /in/. /out
|
||||||
|
cd /out
|
||||||
|
find . -type f -exec sh -c 'gzip -9 -c "$1" > "$1.tmp" && mv "$1.tmp" "$1"' _ {} \;
|
||||||
|
# stop
|
||||||
|
EOT
|
||||||
|
|
||||||
|
FROM scratch AS jaegerui-compressed
|
||||||
|
COPY --from=compressor /out /
|
42
vendor/github.com/tonistiigi/jaeger-ui-rest/config.go
generated
vendored
Normal file
42
vendor/github.com/tonistiigi/jaeger-ui-rest/config.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package jaegerui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Menu struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Items []MenuItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuItem struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Dependencies struct {
|
||||||
|
MenuEnabled bool `json:"menuEnabled"`
|
||||||
|
} `json:"dependencies"`
|
||||||
|
Monitor struct {
|
||||||
|
MenuEnabled bool `json:"menuEnabled"`
|
||||||
|
} `json:"monitor"`
|
||||||
|
ArchiveEnabled bool `json:"archiveEnabled"`
|
||||||
|
Menu []Menu `json:"menu"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) Inject(name string, dt []byte) ([]byte, bool) {
|
||||||
|
if name != "index.html" {
|
||||||
|
return dt, false
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgData, err := json.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return dt, false
|
||||||
|
}
|
||||||
|
|
||||||
|
dt = bytes.Replace(dt, []byte("const JAEGER_CONFIG = DEFAULT_CONFIG;"), slices.Concat([]byte(`const JAEGER_CONFIG = `), cfgData, []byte(`;`)), 1)
|
||||||
|
return dt, true
|
||||||
|
}
|
117
vendor/github.com/tonistiigi/jaeger-ui-rest/decompress/decompress.go
generated
vendored
Normal file
117
vendor/github.com/tonistiigi/jaeger-ui-rest/decompress/decompress.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package decompress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decompressFS struct {
|
||||||
|
fs.FS
|
||||||
|
mu sync.Mutex
|
||||||
|
data map[string][]byte
|
||||||
|
inject Injector
|
||||||
|
}
|
||||||
|
|
||||||
|
type Injector interface {
|
||||||
|
Inject(name string, dt []byte) ([]byte, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFS(fsys fs.FS, injector Injector) fs.FS {
|
||||||
|
return &decompressFS{
|
||||||
|
FS: fsys,
|
||||||
|
data: make(map[string][]byte),
|
||||||
|
inject: injector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decompressFS) Open(name string) (fs.File, error) {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
f, err := d.FS.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
dt, ok := d.data[name]
|
||||||
|
if ok {
|
||||||
|
return &staticFile{
|
||||||
|
Reader: bytes.NewReader(dt),
|
||||||
|
f: f,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.IsDir() {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gzReader, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if _, err := io.Copy(buf, gzReader); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt = buf.Bytes()
|
||||||
|
if d.inject != nil {
|
||||||
|
newdt, ok := d.inject.Inject(name, dt)
|
||||||
|
if ok {
|
||||||
|
dt = newdt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.data[name] = dt
|
||||||
|
|
||||||
|
return &staticFile{
|
||||||
|
Reader: bytes.NewReader(dt),
|
||||||
|
f: f,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type staticFile struct {
|
||||||
|
*bytes.Reader
|
||||||
|
f fs.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticFile) Stat() (fs.FileInfo, error) {
|
||||||
|
fi, err := s.f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &fileInfo{
|
||||||
|
FileInfo: fi,
|
||||||
|
size: int64(s.Len()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticFile) Close() error {
|
||||||
|
return s.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileInfo struct {
|
||||||
|
fs.FileInfo
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileInfo) Size() int64 {
|
||||||
|
return f.size
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fs.File = &staticFile{}
|
3
vendor/github.com/tonistiigi/jaeger-ui-rest/docker-bake.hcl
generated
vendored
Normal file
3
vendor/github.com/tonistiigi/jaeger-ui-rest/docker-bake.hcl
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
target "public" {
|
||||||
|
output = ["./public"]
|
||||||
|
}
|
17
vendor/github.com/tonistiigi/jaeger-ui-rest/fs.go
generated
vendored
Normal file
17
vendor/github.com/tonistiigi/jaeger-ui-rest/fs.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package jaegerui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/tonistiigi/jaeger-ui-rest/decompress"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed public
|
||||||
|
var staticFiles embed.FS
|
||||||
|
|
||||||
|
func FS(cfg Config) http.FileSystem {
|
||||||
|
files, _ := fs.Sub(staticFiles, "public")
|
||||||
|
return http.FS(decompress.NewFS(files, cfg))
|
||||||
|
}
|
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/index.html
generated
vendored
Normal file
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/index.html
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/favicon-BxcVf0am.ico
generated
vendored
Normal file
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/favicon-BxcVf0am.ico
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-BBZlPGVK.js
generated
vendored
Normal file
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-BBZlPGVK.js
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-C4KU8NTf.css
generated
vendored
Normal file
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-C4KU8NTf.css
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/jaeger-logo-CNZsoUdk.svg
generated
vendored
Normal file
BIN
vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/jaeger-logo-CNZsoUdk.svg
generated
vendored
Normal file
Binary file not shown.
3
vendor/github.com/tonistiigi/jaeger-ui-rest/readme.md
generated
vendored
Normal file
3
vendor/github.com/tonistiigi/jaeger-ui-rest/readme.md
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
### jaeger-ui-rest
|
||||||
|
|
||||||
|
[Jaeger UI](https://github.com/jaegertracing/jaeger-ui) server with only the UI component and simple REST API for loading pregenerated JSON traces.
|
172
vendor/github.com/tonistiigi/jaeger-ui-rest/server.go
generated
vendored
Normal file
172
vendor/github.com/tonistiigi/jaeger-ui-rest/server.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package jaegerui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewServer(cfg Config) *Server {
|
||||||
|
mux := &http.ServeMux{}
|
||||||
|
s := &Server{
|
||||||
|
config: cfg,
|
||||||
|
server: &http.Server{
|
||||||
|
Handler: mux,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fsHandler := http.FileServer(FS(cfg))
|
||||||
|
|
||||||
|
mux.HandleFunc("GET /api/services", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"data": [], "total": 0}`))
|
||||||
|
})
|
||||||
|
mux.HandleFunc("GET /trace/", redirectRoot(fsHandler))
|
||||||
|
mux.HandleFunc("GET /search", redirectRoot(fsHandler))
|
||||||
|
|
||||||
|
mux.HandleFunc("POST /api/traces/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
traceID := strings.TrimPrefix(r.URL.Path, "/api/traces/")
|
||||||
|
if traceID == "" || strings.Contains(traceID, "/") {
|
||||||
|
http.Error(w, "Invalid trace ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleHTTPError(s.AddTrace(traceID, r.Body), w)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("GET /api/traces/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
traceID := strings.TrimPrefix(r.URL.Path, "/api/traces/")
|
||||||
|
if traceID == "" {
|
||||||
|
qry := r.URL.Query()
|
||||||
|
ids := qry["traceID"]
|
||||||
|
if len(ids) > 0 {
|
||||||
|
dt, err := s.GetTraces(ids...)
|
||||||
|
if err != nil {
|
||||||
|
handleHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(dt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if traceID == "" || strings.Contains(traceID, "/") {
|
||||||
|
http.Error(w, "Invalid trace ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dt, err := s.GetTraces(traceID)
|
||||||
|
if err != nil {
|
||||||
|
handleHTTPError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(dt)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("/", fsHandler)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
config Config
|
||||||
|
server *http.Server
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
traces map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddTrace(traceID string, rdr io.Reader) error {
|
||||||
|
var payload struct {
|
||||||
|
Data []struct {
|
||||||
|
TraceID string `json:"traceID"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if _, err := io.Copy(buf, rdr); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read trace data")
|
||||||
|
}
|
||||||
|
dt := buf.Bytes()
|
||||||
|
|
||||||
|
if err := json.Unmarshal(dt, &payload); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to unmarshal trace data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(payload.Data) != 1 {
|
||||||
|
return errors.Errorf("expected 1 trace, got %d", len(payload.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Data[0].TraceID != traceID {
|
||||||
|
return errors.Errorf("trace ID mismatch: %s != %s", payload.Data[0].TraceID, traceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.traces == nil {
|
||||||
|
s.traces = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
s.traces[traceID] = dt
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetTraces(traceIDs ...string) ([]byte, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if len(traceIDs) == 0 {
|
||||||
|
return nil, errors.Errorf("trace ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(traceIDs) == 1 {
|
||||||
|
dt, ok := s.traces[traceIDs[0]]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Wrapf(os.ErrNotExist, "trace %s not found", traceIDs[0])
|
||||||
|
}
|
||||||
|
return dt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type payloadT struct {
|
||||||
|
Data []interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
var payload payloadT
|
||||||
|
|
||||||
|
for _, traceID := range traceIDs {
|
||||||
|
dt, ok := s.traces[traceID]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Wrapf(os.ErrNotExist, "trace %s not found", traceID)
|
||||||
|
}
|
||||||
|
var p payloadT
|
||||||
|
if err := json.Unmarshal(dt, &p); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to unmarshal trace data")
|
||||||
|
}
|
||||||
|
payload.Data = append(payload.Data, p.Data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.MarshalIndent(payload, "", " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Serve(l net.Listener) error {
|
||||||
|
return s.server.Serve(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectRoot(h http.Handler) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.URL.Path = "/"
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHTTPError(err error, w http.ResponseWriter) {
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return
|
||||||
|
case errors.Is(err, os.ErrNotExist):
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
default:
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -737,6 +737,10 @@ github.com/tonistiigi/fsutil/types
|
|||||||
# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
|
# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/tonistiigi/go-csvvalue
|
github.com/tonistiigi/go-csvvalue
|
||||||
|
# github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6
|
||||||
|
## explicit; go 1.22.0
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest
|
||||||
|
github.com/tonistiigi/jaeger-ui-rest/decompress
|
||||||
# github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
|
# github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
|
||||||
## explicit
|
## explicit
|
||||||
github.com/tonistiigi/units
|
github.com/tonistiigi/units
|
||||||
|
Loading…
x
Reference in New Issue
Block a user