diff --git a/caddy.go b/caddy.go index 54c890bf3..6964f77ce 100644 --- a/caddy.go +++ b/caddy.go @@ -127,7 +127,9 @@ func Load(cfgJSON []byte, forceReload bool) error { // forcefully reloaded, then errConfigUnchanged This function is safe for // concurrent use. // The ifMatchHeader can optionally be given a string of the format: -// " " +// +// " " +// // where is the absolute path in the config and is the expected hash of // the config at that path. If the hash in the ifMatchHeader doesn't match // the hash of the config, then an APIError with status 412 will be returned. @@ -791,38 +793,102 @@ func InstanceID() (uuid.UUID, error) { return uuid.ParseBytes(uuidFileBytes) } -// GoModule returns the build info of this Caddy -// build from debug.BuildInfo (requires Go modules). -// If no version information is available, a non-nil -// value will still be returned, but with an -// unknown version. -func GoModule() *debug.Module { - var mod debug.Module - return goModule(&mod) -} - -// goModule holds the actual implementation of GoModule. -// Allocating debug.Module in GoModule() and passing a -// reference to goModule enables mid-stack inlining. -func goModule(mod *debug.Module) *debug.Module { - mod.Version = "unknown" +// Version returns the Caddy version in a simple/short form, and +// a full version string. The short form will not have spaces and +// is intended for User-Agent strings and similar, but may be +// omitting valuable information. Note that Caddy must be compiled +// in a special way to properly embed complete version information. +// First this function tries to get the version from the embedded +// build info provided by go.mod dependencies; then it tries to +// get info from embedded VCS information, which requires having +// built Caddy from a git repository. If no version is available, +// this function returns "(devel)" becaise Go uses that, but for +// the simple form we change it to "unknown". +// +// See relevant Go issues: https://github.com/golang/go/issues/29228 +// and https://github.com/golang/go/issues/50603. +// +// This function is experimental and subject to change or removal. +func Version() (simple, full string) { + // the currently-recommended way to build Caddy involves + // building it as a dependency so we can extract version + // information from go.mod tooling; once the upstream + // Go issues are fixed, we should just be able to use + // bi.Main... hopefully. + var module *debug.Module bi, ok := debug.ReadBuildInfo() if ok { - mod.Path = bi.Main.Path - // The recommended way to build Caddy involves - // creating a separate main module, which - // TODO: track related Go issue: https://github.com/golang/go/issues/29228 - // once that issue is fixed, we should just be able to use bi.Main... hopefully. + // find the Caddy module in the dependency list for _, dep := range bi.Deps { if dep.Path == ImportPath { - return dep + module = dep + break } } - return &bi.Main } - return mod + if module != nil { + simple, full = module.Version, module.Version + if module.Sum != "" { + full += " " + module.Sum + } + if module.Replace != nil { + full += " => " + module.Replace.Path + if module.Replace.Version != "" { + simple = module.Replace.Version + "_custom" + full += "@" + module.Replace.Version + } + if module.Replace.Sum != "" { + full += " " + module.Replace.Sum + } + } + } + + if full == "" { + var vcsRevision string + var vcsTime time.Time + var vcsModified bool + for _, setting := range bi.Settings { + switch setting.Key { + case "vcs.revision": + vcsRevision = setting.Value + case "vcs.time": + vcsTime, _ = time.Parse(time.RFC3339, setting.Value) + case "vcs.modified": + vcsModified, _ = strconv.ParseBool(setting.Value) + } + } + + if vcsRevision != "" { + var modified string + if vcsModified { + modified = "+modified" + } + full = fmt.Sprintf("%s%s (%s)", vcsRevision, modified, vcsTime.Format(time.RFC822)) + simple = vcsRevision + + // use short checksum for simple, if hex-only + if _, err := hex.DecodeString(simple); err == nil { + simple = simple[:8] + } + + // append date to simple since it can be convenient + // to know the commit date as part of the version + if !vcsTime.IsZero() { + simple += "-" + vcsTime.Format("20060102") + } + } + } + + if simple == "" || simple == "(devel)" { + simple = "unknown" + } + + return } +// ActiveContext returns the currently-active context. +// This function is experimental and might be changed +// or removed in the future. func ActiveContext() Context { currentCtxMu.RLock() defer currentCtxMu.RUnlock() @@ -867,4 +933,5 @@ var ( var errSameConfig = errors.New("config is unchanged") // ImportPath is the package import path for Caddy core. +// This identifier may be removed in the future. const ImportPath = "github.com/caddyserver/caddy/v2" diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index a4b7bdf75..67015f78d 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -331,30 +331,17 @@ func cmdReload(fl Flags) (int, error) { } func cmdVersion(_ Flags) (int, error) { - fmt.Println(CaddyVersion()) + _, full := caddy.Version() + fmt.Println(full) return caddy.ExitCodeSuccess, nil } -func cmdBuildInfo(fl Flags) (int, error) { +func cmdBuildInfo(_ Flags) (int, error) { bi, ok := debug.ReadBuildInfo() if !ok { return caddy.ExitCodeFailedStartup, fmt.Errorf("no build information") } - - fmt.Printf("go_version: %s\n", runtime.Version()) - fmt.Printf("go_os: %s\n", runtime.GOOS) - fmt.Printf("go_arch: %s\n", runtime.GOARCH) - fmt.Printf("path: %s\n", bi.Path) - fmt.Printf("main: %s %s %s\n", bi.Main.Path, bi.Main.Version, bi.Main.Sum) - fmt.Println("dependencies:") - - for _, goMod := range bi.Deps { - fmt.Printf("%s %s %s", goMod.Path, goMod.Version, goMod.Sum) - if goMod.Replace != nil { - fmt.Printf(" => %s %s %s", goMod.Replace.Path, goMod.Replace.Version, goMod.Replace.Sum) - } - fmt.Println() - } + fmt.Println(bi) return caddy.ExitCodeSuccess, nil } diff --git a/cmd/main.go b/cmd/main.go index e5a4edfe2..e932b6b8a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -38,8 +38,8 @@ import ( func init() { // set a fitting User-Agent for ACME requests - goModule := caddy.GoModule() - cleanModVersion := strings.TrimPrefix(goModule.Version, "v") + version, _ := caddy.Version() + cleanModVersion := strings.TrimPrefix(version, "v") certmagic.UserAgent = "Caddy/" + cleanModVersion // by using Caddy, user indicates agreement to CA terms @@ -441,11 +441,12 @@ func parseEnvFile(envInput io.Reader) (map[string]string, error) { } func printEnvironment() { + _, version := caddy.Version() fmt.Printf("caddy.HomeDir=%s\n", caddy.HomeDir()) fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir()) fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir()) fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath) - fmt.Printf("caddy.Version=%s\n", CaddyVersion()) + fmt.Printf("caddy.Version=%s\n", version) fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS) fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH) fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler) @@ -462,25 +463,6 @@ func printEnvironment() { } } -// CaddyVersion returns a detailed version string, if available. -func CaddyVersion() string { - goModule := caddy.GoModule() - ver := goModule.Version - if goModule.Sum != "" { - ver += " " + goModule.Sum - } - if goModule.Replace != nil { - ver += " => " + goModule.Replace.Path - if goModule.Replace.Version != "" { - ver += "@" + goModule.Replace.Version - } - if goModule.Replace.Sum != "" { - ver += " " + goModule.Replace.Sum - } - } - return ver -} - // StringSlice is a flag.Value that enables repeated use of a string flag. type StringSlice []string diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go index 284813370..313f4ebd1 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go @@ -94,10 +94,8 @@ func (t *Transport) Provision(ctx caddy.Context) error { t.Root = "{http.vars.root}" } - t.serverSoftware = "Caddy" - if mod := caddy.GoModule(); mod.Version != "" { - t.serverSoftware += "/" + mod.Version - } + version, _ := caddy.Version() + t.serverSoftware = "Caddy/" + version // Set a relatively short default dial timeout. // This is helpful to make load-balancer retries more speedy. diff --git a/modules/caddyhttp/tracing/tracer.go b/modules/caddyhttp/tracing/tracer.go index ce23944cf..ddb01e82f 100644 --- a/modules/caddyhttp/tracing/tracer.go +++ b/modules/caddyhttp/tracing/tracer.go @@ -7,7 +7,6 @@ import ( "github.com/caddyserver/caddy/v2" - caddycmd "github.com/caddyserver/caddy/v2/cmd" "github.com/caddyserver/caddy/v2/modules/caddyhttp" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" @@ -52,7 +51,8 @@ func newOpenTelemetryWrapper( spanName: spanName, } - res, err := ot.newResource(webEngineName, caddycmd.CaddyVersion()) + version, _ := caddy.Version() + res, err := ot.newResource(webEngineName, version) if err != nil { return ot, fmt.Errorf("creating resource error: %w", err) }