vendor: update buildkit to v0.14.0-rc1

Update buildkit dependency to v0.14.0-rc1. Update the tracing
infrastructure to use the new detect API which updates how the delegated
exporter is configured.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
Jonathan A. Sternberg
2024-05-31 16:11:33 -05:00
parent 9b6c4103af
commit b1cb658a31
80 changed files with 2583 additions and 1597 deletions

View File

@ -19,7 +19,7 @@ func Helper(u *url.URL) (*connhelper.ConnectionHelper, error) {
if len(addrParts) != 2 {
return nil, errors.Errorf("invalid address %s", u)
}
address := strings.Replace(addrParts[1], "/", "\\", -1)
address := strings.ReplaceAll(addrParts[1], "/", "\\")
return &connhelper.ConnectionHelper{
ContextDialer: func(ctx context.Context, addr string) (net.Conn, error) {
return winio.DialPipeContext(ctx, address)

View File

@ -33,6 +33,19 @@ type Config struct {
DNS *DNSConfig `toml:"dns"`
History *HistoryConfig `toml:"history"`
Frontends struct {
Dockerfile DockerfileFrontendConfig `toml:"dockerfile.v0"`
Gateway GatewayFrontendConfig `toml:"gateway.v0"`
} `toml:"frontend"`
System *SystemConfig `toml:"system"`
}
type SystemConfig struct {
// PlatformCacheMaxAge controls how often supported platforms
// are refreshed by rescanning the system.
PlatformsCacheMaxAge *Duration `toml:"platformsCacheMaxAge"`
}
type LogConfig struct {
@ -40,10 +53,11 @@ type LogConfig struct {
}
type GRPCConfig struct {
Address []string `toml:"address"`
DebugAddress string `toml:"debugAddress"`
UID *int `toml:"uid"`
GID *int `toml:"gid"`
Address []string `toml:"address"`
DebugAddress string `toml:"debugAddress"`
UID *int `toml:"uid"`
GID *int `toml:"gid"`
SecurityDescriptor string `toml:"securityDescriptor"`
TLS TLSConfig `toml:"tls"`
// MaxRecvMsgSize int `toml:"max_recv_message_size"`
@ -154,3 +168,12 @@ type HistoryConfig struct {
MaxAge Duration `toml:"maxAge"`
MaxEntries int64 `toml:"maxEntries"`
}
type DockerfileFrontendConfig struct {
Enabled *bool `toml:"enabled"`
}
type GatewayFrontendConfig struct {
Enabled *bool `toml:"enabled"`
AllowedRepositories []string `toml:"allowedRepositories"`
}

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/docker/go-units"
"github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors"
)
@ -104,3 +105,25 @@ func stripQuotes(s string) string {
}
return s
}
func DetectDefaultGCCap() DiskSpace {
return DiskSpace{Percentage: DiskSpacePercentage}
}
func (d DiskSpace) AsBytes(root string) int64 {
if d.Bytes != 0 {
return d.Bytes
}
if d.Percentage == 0 {
return 0
}
diskSize, err := getDiskSize(root)
if err != nil {
bklog.L.Warnf("failed to get disk size: %v", err)
return defaultCap
}
avail := diskSize * d.Percentage / 100
rounded := (avail/(1<<30) + 1) * 1e9 // round up
return rounded
}

View File

@ -0,0 +1,19 @@
//go:build openbsd
// +build openbsd
package config
import (
"syscall"
)
var DiskSpacePercentage int64 = 10
func getDiskSize(root string) (int64, error) {
var st syscall.Statfs_t
if err := syscall.Statfs(root, &st); err != nil {
return 0, err
}
diskSize := int64(st.F_bsize) * int64(st.F_blocks)
return diskSize, nil
}

View File

@ -1,5 +1,5 @@
//go:build !windows
// +build !windows
//go:build !windows && !openbsd
// +build !windows,!openbsd
package config
@ -7,23 +7,13 @@ import (
"syscall"
)
func DetectDefaultGCCap() DiskSpace {
return DiskSpace{Percentage: 10}
}
func (d DiskSpace) AsBytes(root string) int64 {
if d.Bytes != 0 {
return d.Bytes
}
if d.Percentage == 0 {
return 0
}
var DiskSpacePercentage int64 = 10
func getDiskSize(root string) (int64, error) {
var st syscall.Statfs_t
if err := syscall.Statfs(root, &st); err != nil {
return defaultCap
return 0, err
}
diskSize := int64(st.Bsize) * int64(st.Blocks)
avail := diskSize * d.Percentage / 100
return (avail/(1<<30) + 1) * 1e9 // round up
return diskSize, nil
}

View File

@ -3,10 +3,29 @@
package config
func DetectDefaultGCCap() DiskSpace {
return DiskSpace{Bytes: defaultCap}
}
import (
"golang.org/x/sys/windows"
)
func (d DiskSpace) AsBytes(root string) int64 {
return d.Bytes
// set as double that for Linux since
// Windows images are generally larger.
var DiskSpacePercentage int64 = 20
func getDiskSize(root string) (int64, error) {
rootUTF16, err := windows.UTF16FromString(root)
if err != nil {
return 0, err
}
var freeAvailableBytes uint64
var totalBytes uint64
var totalFreeBytes uint64
if err := windows.GetDiskFreeSpaceEx(
&rootUTF16[0],
&freeAvailableBytes,
&totalBytes,
&totalFreeBytes); err != nil {
return 0, err
}
return int64(totalBytes), nil
}

View File

@ -13,12 +13,14 @@ import (
const (
keySyntax = "syntax"
keyCheck = "check"
keyEscape = "escape"
)
var validDirectives = map[string]struct{}{
keySyntax: {},
keyEscape: {},
keyCheck: {},
}
type Directive struct {
@ -110,6 +112,10 @@ func (d *DirectiveParser) ParseAll(data []byte) ([]*Directive, error) {
// This allows for a flexible range of input formats, and appropriate syntax
// selection.
func DetectSyntax(dt []byte) (string, string, []Range, bool) {
return ParseDirective(keySyntax, dt)
}
func ParseDirective(key string, dt []byte) (string, string, []Range, bool) {
dt, hadShebang, err := discardShebang(dt)
if err != nil {
return "", "", nil, false
@ -119,42 +125,38 @@ func DetectSyntax(dt []byte) (string, string, []Range, bool) {
line++
}
// use default directive parser, and search for #syntax=
// use default directive parser, and search for #key=
directiveParser := DirectiveParser{line: line}
if syntax, cmdline, loc, ok := detectSyntaxFromParser(dt, directiveParser); ok {
if syntax, cmdline, loc, ok := detectDirectiveFromParser(key, dt, directiveParser); ok {
return syntax, cmdline, loc, true
}
// use directive with different comment prefix, and search for //syntax=
// use directive with different comment prefix, and search for //key=
directiveParser = DirectiveParser{line: line}
directiveParser.setComment("//")
if syntax, cmdline, loc, ok := detectSyntaxFromParser(dt, directiveParser); ok {
if syntax, cmdline, loc, ok := detectDirectiveFromParser(key, dt, directiveParser); ok {
return syntax, cmdline, loc, true
}
// search for possible json directives
var directive struct {
Syntax string `json:"syntax"`
}
if err := json.Unmarshal(dt, &directive); err == nil {
if directive.Syntax != "" {
// use json directive, and search for { "key": "..." }
jsonDirective := map[string]string{}
if err := json.Unmarshal(dt, &jsonDirective); err == nil {
if v, ok := jsonDirective[key]; ok {
loc := []Range{{
Start: Position{Line: line},
End: Position{Line: line},
}}
return directive.Syntax, directive.Syntax, loc, true
return v, v, loc, true
}
}
return "", "", nil, false
}
func detectSyntaxFromParser(dt []byte, parser DirectiveParser) (string, string, []Range, bool) {
func detectDirectiveFromParser(key string, dt []byte, parser DirectiveParser) (string, string, []Range, bool) {
directives, _ := parser.ParseAll(dt)
for _, d := range directives {
// check for syntax directive before erroring out, since the error
// might have occurred *after* the syntax directive
if d.Name == keySyntax {
if d.Name == key {
p, _, _ := strings.Cut(d.Value, " ")
return p, d.Value, d.Location, true
}

View File

@ -154,7 +154,7 @@ func parseNameVal(rest string, key string, d *directives) (*Node, error) {
if len(parts) < 2 {
return nil, errors.Errorf("%s must have two arguments", key)
}
return newKeyValueNode(parts[0], parts[1]), nil
return newKeyValueNode(parts[0], parts[1], ""), nil
}
var rootNode *Node
@ -165,17 +165,20 @@ func parseNameVal(rest string, key string, d *directives) (*Node, error) {
}
parts := strings.SplitN(word, "=", 2)
node := newKeyValueNode(parts[0], parts[1])
node := newKeyValueNode(parts[0], parts[1], "=")
rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
}
return rootNode, nil
}
func newKeyValueNode(key, value string) *Node {
func newKeyValueNode(key, value, sep string) *Node {
return &Node{
Value: key,
Next: &Node{Value: value},
Next: &Node{
Value: value,
Next: &Node{Value: sep},
},
}
}
@ -187,7 +190,9 @@ func appendKeyValueNode(node, rootNode, prevNode *Node) (*Node, *Node) {
prevNode.Next = node
}
prevNode = node.Next
for prevNode = node.Next; prevNode.Next != nil; {
prevNode = prevNode.Next
}
return rootNode, prevNode
}

View File

@ -32,10 +32,12 @@ func NewLex(escapeToken rune) *Lex {
}
// ProcessWord will use the 'env' list of environment variables,
// and replace any env var references in 'word'.
func (s *Lex) ProcessWord(word string, env []string) (string, error) {
word, _, err := s.process(word, BuildEnvs(env))
return word, err
// and replace any env var references in 'word'. It will also
// return variables in word which were not found in the 'env' list,
// which is useful in later linting.
func (s *Lex) ProcessWord(word string, env []string) (string, map[string]struct{}, error) {
result, err := s.process(word, BuildEnvs(env))
return result.Result, result.Unmatched, err
}
// ProcessWords will use the 'env' list of environment variables,
@ -46,28 +48,40 @@ func (s *Lex) ProcessWord(word string, env []string) (string, error) {
// Note, each one is trimmed to remove leading and trailing spaces (unless
// they are quoted", but ProcessWord retains spaces between words.
func (s *Lex) ProcessWords(word string, env []string) ([]string, error) {
_, words, err := s.process(word, BuildEnvs(env))
return words, err
result, err := s.process(word, BuildEnvs(env))
return result.Words, err
}
// ProcessWordWithMap will use the 'env' list of environment variables,
// and replace any env var references in 'word'.
func (s *Lex) ProcessWordWithMap(word string, env map[string]string) (string, error) {
word, _, err := s.process(word, env)
return word, err
result, err := s.process(word, env)
return result.Result, err
}
type ProcessWordResult struct {
Result string
Words []string
Matched map[string]struct{}
Unmatched map[string]struct{}
}
// ProcessWordWithMatches will use the 'env' list of environment variables,
// replace any env var references in 'word' and return the env that were used.
func (s *Lex) ProcessWordWithMatches(word string, env map[string]string) (string, map[string]struct{}, error) {
func (s *Lex) ProcessWordWithMatches(word string, env map[string]string) (ProcessWordResult, error) {
sw := s.init(word, env)
word, _, err := sw.process(word)
return word, sw.matches, err
word, words, err := sw.process(word)
return ProcessWordResult{
Result: word,
Words: words,
Matched: sw.matches,
Unmatched: sw.nonmatches,
}, err
}
func (s *Lex) ProcessWordsWithMap(word string, env map[string]string) ([]string, error) {
_, words, err := s.process(word, env)
return words, err
result, err := s.process(word, env)
return result.Words, err
}
func (s *Lex) init(word string, env map[string]string) *shellWord {
@ -79,14 +93,21 @@ func (s *Lex) init(word string, env map[string]string) *shellWord {
rawQuotes: s.RawQuotes,
rawEscapes: s.RawEscapes,
matches: make(map[string]struct{}),
nonmatches: make(map[string]struct{}),
}
sw.scanner.Init(strings.NewReader(word))
return sw
}
func (s *Lex) process(word string, env map[string]string) (string, []string, error) {
func (s *Lex) process(word string, env map[string]string) (*ProcessWordResult, error) {
sw := s.init(word, env)
return sw.process(word)
word, words, err := sw.process(word)
return &ProcessWordResult{
Result: word,
Words: words,
Matched: sw.matches,
Unmatched: sw.nonmatches,
}, err
}
type shellWord struct {
@ -98,6 +119,7 @@ type shellWord struct {
skipUnsetEnv bool
skipProcessQuotes bool
matches map[string]struct{}
nonmatches map[string]struct{}
}
func (sw *shellWord) process(source string) (string, []string, error) {
@ -511,6 +533,7 @@ func (sw *shellWord) getEnv(name string) (string, bool) {
return value, true
}
}
sw.nonmatches[name] = struct{}{}
return "", false
}

View File

@ -47,6 +47,7 @@ const (
keyCacheNSArg = "build-arg:BUILDKIT_CACHE_MOUNT_NS"
keyMultiPlatformArg = "build-arg:BUILDKIT_MULTI_PLATFORM"
keyHostnameArg = "build-arg:BUILDKIT_SANDBOX_HOSTNAME"
keyDockerfileLintArg = "build-arg:BUILDKIT_DOCKERFILE_CHECK"
keyContextKeepGitDirArg = "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR"
keySourceDateEpoch = "build-arg:SOURCE_DATE_EPOCH"
)
@ -64,6 +65,7 @@ type Config struct {
ShmSize int64
Target string
Ulimits []pb.Ulimit
LinterConfig *string
CacheImports []client.CacheOptionsEntry
TargetPlatforms []ocispecs.Platform // nil means default
@ -277,6 +279,10 @@ func (bc *Client) init() error {
opts[keyHostname] = v
}
bc.Hostname = opts[keyHostname]
if v, ok := opts[keyDockerfileLintArg]; ok {
bc.LinterConfig = &v
}
return nil
}

View File

@ -1249,11 +1249,18 @@ func (r *reference) StatFile(ctx context.Context, req client.StatRequest) (*fsty
}
func grpcClientConn(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
dialOpt := grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return stdioConn(), nil
})
dialOpts := []grpc.DialOption{
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return stdioConn(), nil
}),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor),
grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(16 << 20)),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(16 << 20)),
}
cc, err := grpc.DialContext(ctx, "localhost", dialOpt, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor), grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor))
cc, err := grpc.DialContext(ctx, "localhost", dialOpts...)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to create grpc client")
}

View File

@ -39,9 +39,15 @@ type Warning struct {
Location pb.Location `json:"location,omitempty"`
}
type BuildError struct {
Message string `json:"message"`
Location pb.Location `json:"location"`
}
type LintResults struct {
Warnings []Warning `json:"warnings"`
Sources []*pb.SourceInfo `json:"sources"`
Error *BuildError `json:"buildError,omitempty"`
}
func (results *LintResults) AddSource(sourceMap *llb.SourceMap) int {

View File

@ -72,10 +72,8 @@ func PrintTargets(dt []byte, w io.Writer) error {
name := t.Name
if name == "" && t.Default {
name = "(default)"
} else {
if t.Default {
name = fmt.Sprintf("%s (default)", name)
}
} else if t.Default {
name = fmt.Sprintf("%s (default)", name)
}
fmt.Fprintf(tw, "%s\t%s\n", name, t.Description)
}

View File

@ -166,6 +166,7 @@ func toAgentSource(paths []string) (source, error) {
return source{}, errors.Wrapf(err, "failed to open %s", p)
}
dt, err := io.ReadAll(&io.LimitedReader{R: f, N: 100 * 1024})
_ = f.Close()
if err != nil {
return source{}, errors.Wrapf(err, "failed to read %s", p)
}

View File

@ -210,13 +210,23 @@ func (cli *GitCLI) Run(ctx context.Context, args ...string) (_ []byte, err error
}
if err != nil {
select {
case <-ctx.Done():
cerr := context.Cause(ctx)
if cerr != nil {
return buf.Bytes(), errors.Wrapf(cerr, "context completed: git stderr:\n%s", errbuf.String())
}
default:
}
if strings.Contains(errbuf.String(), "--depth") || strings.Contains(errbuf.String(), "shallow") {
if newArgs := argsNoDepth(args); len(args) > len(newArgs) {
args = newArgs
continue
}
}
return buf.Bytes(), errors.Errorf("git error: %s\nstderr:\n%s", err, errbuf.String())
return buf.Bytes(), errors.Wrapf(err, "git stderr:\n%s", errbuf.String())
}
return buf.Bytes(), nil
}

View File

@ -95,6 +95,7 @@ func (mr *MultiReader) Reader(ctx context.Context) Reader {
mr.mu.Lock()
defer mr.mu.Unlock()
delete(mr.writers, w)
closeWriter(context.Cause(ctx))
}()
if !mr.initialized {

View File

@ -285,7 +285,6 @@ type rawJSONDisplay struct {
// output of status update events.
func newRawJSONDisplay(w io.Writer) Display {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return Display{
disp: &rawJSONDisplay{
enc: enc,
@ -744,6 +743,7 @@ func (t *trace) update(s *client.SolveStatus, termWidth int) {
v.jobCached = false
if v.term != nil {
if v.term.Width != termWidth {
termHeight = max(termHeightMin, min(termHeightInitial, v.term.Height-termHeightMin-1))
v.term.Resize(termHeight, termWidth-termPad)
}
v.termBytes += len(l.Data)
@ -823,7 +823,7 @@ func (t *trace) displayInfo() (d displayInfo) {
}
var jobs []*job
j := &job{
name: strings.Replace(v.Name, "\t", " ", -1),
name: strings.ReplaceAll(v.Name, "\t", " "),
vertex: v,
isCompleted: true,
}
@ -913,7 +913,7 @@ func addTime(tm *time.Time, d time.Duration) *time.Time {
if tm == nil {
return nil
}
t := (*tm).Add(d)
t := tm.Add(d)
return &t
}
@ -957,6 +957,7 @@ func setupTerminals(jobs []*job, height int, all bool) []*job {
numFree := height - 2 - numInUse
numToHide := 0
termHeight = max(termHeightMin, min(termHeightInitial, height-termHeightMin-1))
termLimit := termHeight + 3
for i := 0; numFree > termLimit && i < len(candidates); i++ {

View File

@ -13,7 +13,10 @@ var colorCancel aec.ANSI
var colorWarning aec.ANSI
var colorError aec.ANSI
var termHeight = 6
const termHeightMin = 6
var termHeightInitial = termHeightMin
var termHeight = termHeightMin
func init() {
// As recommended on https://no-color.org/
@ -43,6 +46,7 @@ func init() {
if termHeightStr != "" {
termHeightVal, err := strconv.Atoi(termHeightStr)
if err == nil && termHeightVal > 0 {
termHeightInitial = termHeightVal
termHeight = termHeightVal
}
}

View File

@ -89,7 +89,7 @@ func ToSlash(inputPath, inputOS string) string {
if inputOS != "windows" {
return inputPath
}
return strings.Replace(inputPath, "\\", "/", -1)
return strings.ReplaceAll(inputPath, "\\", "/")
}
func FromSlash(inputPath, inputOS string) string {
@ -97,7 +97,7 @@ func FromSlash(inputPath, inputOS string) string {
if inputOS == "windows" {
separator = "\\"
}
return strings.Replace(inputPath, "/", separator, -1)
return strings.ReplaceAll(inputPath, "/", separator)
}
// NormalizeWorkdir will return a normalized version of the new workdir, given

View File

@ -21,6 +21,7 @@ import (
"github.com/moby/buildkit/util/appcontext"
"github.com/moby/buildkit/util/contentutil"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"golang.org/x/sync/semaphore"
)
@ -197,6 +198,9 @@ func Run(t *testing.T, testCases []Test, opt ...TestOpt) {
require.NoError(t, sandboxLimiter.Acquire(context.TODO(), 1))
defer sandboxLimiter.Release(1)
ctx, cancel := context.WithCancelCause(ctx)
defer cancel(errors.WithStack(context.Canceled))
sb, closer, err := newSandbox(ctx, br, mirror, mv)
require.NoError(t, err)
t.Cleanup(func() { _ = closer() })

View File

@ -7,6 +7,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@ -65,6 +66,10 @@ func (sb *sandbox) Cmd(args ...string) *exec.Cmd {
cmd := exec.Command("buildctl", args...)
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, "BUILDKIT_HOST="+sb.Address())
if v := os.Getenv("GO_TEST_COVERPROFILE"); v != "" {
coverDir := filepath.Join(filepath.Dir(v), "helpers")
cmd.Env = append(cmd.Env, "GOCOVERDIR="+coverDir)
}
return cmd
}

View File

@ -79,6 +79,10 @@ func runBuildkitd(
"BUILDKIT_DEBUG_EXEC_OUTPUT=1",
"BUILDKIT_DEBUG_PANIC_ON_ERROR=1",
"TMPDIR="+filepath.Join(tmpdir, "tmp"))
if v := os.Getenv("GO_TEST_COVERPROFILE"); v != "" {
coverDir := filepath.Join(filepath.Dir(v), "helpers")
cmd.Env = append(cmd.Env, "GOCOVERDIR="+coverDir)
}
cmd.Env = append(cmd.Env, extraEnv...)
cmd.SysProcAttr = getSysProcAttr()

View File

@ -4,19 +4,13 @@ import (
"context"
"sync"
"github.com/moby/buildkit/util/tracing/detect"
"github.com/moby/buildkit/client"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
const maxBuffer = 256
var exp = &Exporter{}
func init() {
detect.Register("delegated", detect.TraceExporterDetector(func() (sdktrace.SpanExporter, error) {
return exp, nil
}), 100)
}
var DefaultExporter = &Exporter{}
type Exporter struct {
mu sync.Mutex
@ -24,7 +18,10 @@ type Exporter struct {
buffer []sdktrace.ReadOnlySpan
}
var _ sdktrace.SpanExporter = &Exporter{}
var (
_ sdktrace.SpanExporter = (*Exporter)(nil)
_ client.TracerDelegate = (*Exporter)(nil)
)
func (e *Exporter) ExportSpans(ctx context.Context, ss []sdktrace.ReadOnlySpan) error {
e.mu.Lock()

View File

@ -3,22 +3,13 @@ package detect
import (
"context"
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/metric"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
type ExporterDetector interface {
@ -31,18 +22,7 @@ type detector struct {
priority int
}
var ServiceName string
var detectors map[string]detector
var once sync.Once
var tp trace.TracerProvider
var mp metric.MeterProvider
var exporter struct {
SpanExporter sdktrace.SpanExporter
MetricExporter sdkmetric.Exporter
}
var closers []func(context.Context) error
var err error
func Register(name string, exp ExporterDetector, priority int) {
if detectors == nil {
@ -64,32 +44,21 @@ func (fn TraceExporterDetector) DetectMetricExporter() (sdkmetric.Exporter, erro
return nil, nil
}
func detectExporters() (texp sdktrace.SpanExporter, mexp sdkmetric.Exporter, err error) {
texp, err = detectExporter("OTEL_TRACES_EXPORTER", func(d ExporterDetector) (sdktrace.SpanExporter, bool, error) {
exp, err := d.DetectTraceExporter()
return exp, exp != nil, err
})
if err != nil {
return nil, nil, err
}
mexp, err = detectExporter("OTEL_METRICS_EXPORTER", func(d ExporterDetector) (sdkmetric.Exporter, bool, error) {
exp, err := d.DetectMetricExporter()
return exp, exp != nil, err
})
if err != nil {
return nil, nil, err
}
return texp, mexp, nil
}
func detectExporter[T any](envVar string, fn func(d ExporterDetector) (T, bool, error)) (exp T, err error) {
ignoreErrors, _ := strconv.ParseBool("OTEL_IGNORE_ERROR")
if n := os.Getenv(envVar); n != "" {
d, ok := detectors[n]
if !ok {
return exp, errors.Errorf("unsupported opentelemetry exporter %v", n)
if !ignoreErrors {
err = errors.Errorf("unsupported opentelemetry exporter %v", n)
}
return exp, err
}
exp, _, err = fn(d.f)
if err != nil && ignoreErrors {
err = nil
}
return exp, err
}
@ -104,7 +73,7 @@ func detectExporter[T any](envVar string, fn func(d ExporterDetector) (T, bool,
var ok bool
for _, d := range arr {
exp, ok, err = fn(d.f)
if err != nil {
if err != nil && !ignoreErrors {
return exp, err
}
@ -115,164 +84,70 @@ func detectExporter[T any](envVar string, fn func(d ExporterDetector) (T, bool,
return exp, nil
}
func detect() error {
tp = noop.NewTracerProvider()
mp = sdkmetric.NewMeterProvider()
texp, mexp, err := detectExporters()
if err != nil || (texp == nil && mexp == nil) {
return err
}
res := Resource()
if texp != nil || Recorder != nil {
// enable log with traceID when a valid exporter is used
bklog.EnableLogWithTraceID(true)
sdktpopts := []sdktrace.TracerProviderOption{
sdktrace.WithResource(res),
}
if texp != nil {
sdktpopts = append(sdktpopts, sdktrace.WithBatcher(texp))
}
if Recorder != nil {
sp := sdktrace.NewSimpleSpanProcessor(Recorder)
sdktpopts = append(sdktpopts, sdktrace.WithSpanProcessor(sp))
}
sdktp := sdktrace.NewTracerProvider(sdktpopts...)
closers = append(closers, sdktp.Shutdown)
exporter.SpanExporter = texp
tp = sdktp
}
var readers []sdkmetric.Reader
if mexp != nil {
// Create a new periodic reader using any configured metric exporter.
readers = append(readers, sdkmetric.NewPeriodicReader(mexp))
}
if r, err := prometheus.New(); err != nil {
// Log the error but do not fail if we could not configure the prometheus metrics.
bklog.G(context.Background()).
WithError(err).
Error("failed prometheus metrics configuration")
} else {
// Register the prometheus reader if there was no error.
readers = append(readers, r)
}
if len(readers) > 0 {
opts := make([]sdkmetric.Option, 0, len(readers)+1)
opts = append(opts, sdkmetric.WithResource(res))
for _, r := range readers {
opts = append(opts, sdkmetric.WithReader(r))
}
sdkmp := sdkmetric.NewMeterProvider(opts...)
closers = append(closers, sdkmp.Shutdown)
exporter.MetricExporter = mexp
mp = sdkmp
}
return nil
}
func TracerProvider() (trace.TracerProvider, error) {
if err := detectOnce(); err != nil {
return nil, err
}
return tp, nil
}
func MeterProvider() (metric.MeterProvider, error) {
if err := detectOnce(); err != nil {
return nil, err
}
return mp, nil
}
func detectOnce() error {
once.Do(func() {
if err1 := detect(); err1 != nil {
b, _ := strconv.ParseBool(os.Getenv("OTEL_IGNORE_ERROR"))
if !b {
err = err1
}
}
})
return err
}
func Exporter() (sdktrace.SpanExporter, sdkmetric.Exporter, error) {
_, err := TracerProvider()
if err != nil {
return nil, nil, err
}
return exporter.SpanExporter, exporter.MetricExporter, nil
}
func Shutdown(ctx context.Context) error {
for _, c := range closers {
if err := c(ctx); err != nil {
return err
}
}
return nil
}
var (
detectedResource *resource.Resource
detectedResourceOnce sync.Once
)
func Resource() *resource.Resource {
detectedResourceOnce.Do(func() {
res, err := resource.New(context.Background(),
resource.WithDetectors(serviceNameDetector{}),
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
)
if err != nil {
otel.Handle(err)
}
detectedResource = res
})
return detectedResource
}
// OverrideResource overrides the resource returned from Resource.
//
// This must be invoked before Resource is called otherwise it is a no-op.
func OverrideResource(res *resource.Resource) {
detectedResourceOnce.Do(func() {
detectedResource = res
func NewSpanExporter(_ context.Context) (sdktrace.SpanExporter, error) {
return detectExporter("OTEL_TRACES_EXPORTER", func(d ExporterDetector) (sdktrace.SpanExporter, bool, error) {
exp, err := d.DetectTraceExporter()
return exp, exp != nil, err
})
}
type serviceNameDetector struct{}
func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, error) {
return resource.StringDetector(
semconv.SchemaURL,
semconv.ServiceNameKey,
func() (string, error) {
if ServiceName != "" {
return ServiceName, nil
}
return filepath.Base(os.Args[0]), nil
},
).Detect(ctx)
func NewMetricExporter(_ context.Context) (sdkmetric.Exporter, error) {
return detectExporter("OTEL_METRICS_EXPORTER", func(d ExporterDetector) (sdkmetric.Exporter, bool, error) {
exp, err := d.DetectMetricExporter()
return exp, exp != nil, err
})
}
type noneDetector struct{}
func (n noneDetector) DetectTraceExporter() (sdktrace.SpanExporter, error) {
return nil, nil
return noneSpanExporter{}, nil
}
func (n noneDetector) DetectMetricExporter() (sdkmetric.Exporter, error) {
return nil, nil
return noneMetricExporter{}, nil
}
type noneSpanExporter struct{}
func (n noneSpanExporter) ExportSpans(_ context.Context, _ []sdktrace.ReadOnlySpan) error {
return nil
}
func (n noneSpanExporter) Shutdown(_ context.Context) error {
return nil
}
func IsNoneSpanExporter(exp sdktrace.SpanExporter) bool {
_, ok := exp.(noneSpanExporter)
return ok
}
type noneMetricExporter struct{}
func (n noneMetricExporter) Temporality(kind sdkmetric.InstrumentKind) metricdata.Temporality {
return sdkmetric.DefaultTemporalitySelector(kind)
}
func (n noneMetricExporter) Aggregation(kind sdkmetric.InstrumentKind) sdkmetric.Aggregation {
return sdkmetric.DefaultAggregationSelector(kind)
}
func (n noneMetricExporter) Export(_ context.Context, _ *metricdata.ResourceMetrics) error {
return nil
}
func (n noneMetricExporter) ForceFlush(_ context.Context) error {
return nil
}
func (n noneMetricExporter) Shutdown(_ context.Context) error {
return nil
}
func IsNoneMetricExporter(exp sdkmetric.Exporter) bool {
_, ok := exp.(noneMetricExporter)
return ok
}
func init() {

View File

@ -0,0 +1,58 @@
package detect
import (
"context"
"os"
"path/filepath"
"sync"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
var (
ServiceName string
detectedResource *resource.Resource
detectedResourceOnce sync.Once
)
func Resource() *resource.Resource {
detectedResourceOnce.Do(func() {
res, err := resource.New(context.Background(),
resource.WithDetectors(serviceNameDetector{}),
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
)
if err != nil {
otel.Handle(err)
}
detectedResource = res
})
return detectedResource
}
// OverrideResource overrides the resource returned from Resource.
//
// This must be invoked before Resource is called otherwise it is a no-op.
func OverrideResource(res *resource.Resource) {
detectedResourceOnce.Do(func() {
detectedResource = res
})
}
type serviceNameDetector struct{}
func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, error) {
return resource.StringDetector(
semconv.SchemaURL,
semconv.ServiceNameKey,
func() (string, error) {
if ServiceName != "" {
return ServiceName, nil
}
return filepath.Base(os.Args[0]), nil
},
).Detect(ctx)
}

View File

@ -0,0 +1,36 @@
package tracing
import (
"context"
"github.com/hashicorp/go-multierror"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
type MultiSpanExporter []sdktrace.SpanExporter
func (m MultiSpanExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) (err error) {
for _, exp := range m {
if e := exp.ExportSpans(ctx, spans); e != nil {
if err != nil {
err = multierror.Append(err, e)
continue
}
err = e
}
}
return err
}
func (m MultiSpanExporter) Shutdown(ctx context.Context) (err error) {
for _, exp := range m {
if e := exp.Shutdown(ctx); e != nil {
if err != nil {
err = multierror.Append(err, e)
continue
}
err = e
}
}
return err
}