Files
moby/dockerversion/useragent.go
Sebastiaan van Stijn 66dfc0169f dockerversion: DockerUserAgent(): use sync.Once to construct User-Agent
The User-Agent includes the kernel version, which involves making a syscall
(and parsing the results) on Linux, and reading (plus parsing) the registry
on Windows. These operations are relatively costly, and we should not perform
those on every request that uses the User-Agent.

This patch adds a sync.Once so that we only perform these actions once for
the lifetime of the daemon's process.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-03-22 15:35:59 +01:00

100 lines
3.0 KiB
Go

package dockerversion // import "github.com/docker/docker/dockerversion"
import (
"context"
"fmt"
"runtime"
"sync"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/useragent"
)
// UAStringKey is used as key type for user-agent string in net/context struct
type UAStringKey struct{}
// DockerUserAgent is the User-Agent the Docker client uses to identify itself.
// In accordance with RFC 7231 (5.5.3) is of the form:
//
// [docker client's UA] UpstreamClient([upstream client's UA])
func DockerUserAgent(ctx context.Context) string {
daemonUA := getDaemonUserAgent()
if upstreamUA := getUserAgentFromContext(ctx); len(upstreamUA) > 0 {
return insertUpstreamUserAgent(upstreamUA, daemonUA)
}
return daemonUA
}
var (
daemonUAOnce sync.Once
daemonUA string
)
// getUserAgentFromContext returns the user-agent to use for requests made by
// the daemon.
//
// It includes;
//
// - the docker version
// - go version
// - git-commit
// - kernel version
// - os
// - architecture
func getDaemonUserAgent() string {
daemonUAOnce.Do(func() {
httpVersion := make([]useragent.VersionInfo, 0, 6)
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
}
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
daemonUA = useragent.AppendVersions("", httpVersion...)
})
return daemonUA
}
// getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
func getUserAgentFromContext(ctx context.Context) string {
var upstreamUA string
if ctx != nil {
var ki interface{} = ctx.Value(UAStringKey{})
if ki != nil {
upstreamUA = ctx.Value(UAStringKey{}).(string)
}
}
return upstreamUA
}
// escapeStr returns s with every rune in charsToEscape escaped by a backslash
func escapeStr(s string, charsToEscape string) string {
var ret string
for _, currRune := range s {
appended := false
for _, escapableRune := range charsToEscape {
if currRune == escapableRune {
ret += `\` + string(currRune)
appended = true
break
}
}
if !appended {
ret += string(currRune)
}
}
return ret
}
// insertUpstreamUserAgent adds the upstream client useragent to create a user-agent
// string of the form:
//
// $dockerUA UpstreamClient($upstreamUA)
func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string {
charsToEscape := `();\`
upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape)
return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped)
}