Merge pull request #13357 from hashicorp/ma/add-build-date-oss

Add build date (oss)
This commit is contained in:
Mark Anderson 2022-06-13 08:43:20 -07:00 committed by GitHub
commit edbf19f4e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 209 additions and 22 deletions

4
.changelog/13357.txt Normal file
View File

@ -0,0 +1,4 @@
```release-note:feature
agent: Added information about build date alongside other version information for Consul. Extended /agent/self endpoint and `consul version` commands
to report this. Agent also reports build date in log on startup.
```

View File

@ -4,4 +4,7 @@ export GIT_COMMIT=$(git rev-parse --short HEAD)
export GIT_COMMIT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD) export GIT_COMMIT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD)
export GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true) export GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
export GIT_IMPORT=github.com/hashicorp/consul/version export GIT_IMPORT=github.com/hashicorp/consul/version
export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY}" # we're using this for build date because it's stable across platform builds
# the env -i and -noprofile are used to ensure we don't try to recursively call this profile when starting bash
export GIT_DATE=$(env -i /bin/bash --noprofile -norc ${CIRCLE_WORKING_DIRECTORY}/build-support/scripts/build-date.sh)
export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${GIT_IMPORT}.BuildDate=${GIT_DATE}"

View File

@ -15,6 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
product-version: ${{ steps.get-product-version.outputs.product-version }} product-version: ${{ steps.get-product-version.outputs.product-version }}
product-date: ${{ steps.get-product-version.outputs.product-date }}
pre-version: ${{ steps.get-product-version.outputs.pre-version }} pre-version: ${{ steps.get-product-version.outputs.pre-version }}
pkg-version: ${{ steps.get-product-version.outputs.pkg-version }} pkg-version: ${{ steps.get-product-version.outputs.pkg-version }}
shared-ldflags: ${{ steps.shared-ldflags.outputs.shared-ldflags }} shared-ldflags: ${{ steps.shared-ldflags.outputs.shared-ldflags }}
@ -24,6 +25,7 @@ jobs:
id: get-product-version id: get-product-version
run: | run: |
CONSUL_VERSION=$(build-support/scripts/version.sh -r) CONSUL_VERSION=$(build-support/scripts/version.sh -r)
CONSUL_DATE=$(build-support/scripts/build-date.sh)
## TODO: This assumes `make version` outputs 1.1.1+ent-prerel ## TODO: This assumes `make version` outputs 1.1.1+ent-prerel
IFS="+" read VERSION _other <<< "$CONSUL_VERSION" IFS="+" read VERSION _other <<< "$CONSUL_VERSION"
IFS="-" read _other PREREL_VERSION <<< "$CONSUL_VERSION" IFS="-" read _other PREREL_VERSION <<< "$CONSUL_VERSION"
@ -32,12 +34,15 @@ jobs:
## [version]{-prerelease}+ent before then, we'll need to add ## [version]{-prerelease}+ent before then, we'll need to add
## logic to handle presense/absence of the prerelease ## logic to handle presense/absence of the prerelease
echo "::set-output name=product-version::${CONSUL_VERSION}" echo "::set-output name=product-version::${CONSUL_VERSION}"
echo "::set-output name=product-date::${CONSUL_DATE}"
echo "::set-output name=pre-version::${PREREL_VERSION}" echo "::set-output name=pre-version::${PREREL_VERSION}"
echo "::set-output name=pkg-version::${VERSION}" echo "::set-output name=pkg-version::${VERSION}"
- name: Set shared -ldflags - name: Set shared -ldflags
id: shared-ldflags id: shared-ldflags
run: echo "::set-output name=shared-ldflags::-X github.com/hashicorp/consul/version.GitCommit=${GITHUB_SHA::8} -X github.com/hashicorp/consul/version.GitDescribe=${{ steps.get-product-version.outputs.product-version }}" run: |
T="github.com/hashicorp/consul/version"
echo "::set-output name=shared-ldflags::-X ${T}.GitCommit=${GITHUB_SHA::8} -X ${T}.GitDescribe=${{ steps.get-product-version.outputs.product-version }} -X ${T}.BuildDate=${{ steps.get-product-version.outputs.product-date }}"
generate-metadata-file: generate-metadata-file:
needs: get-product-version needs: get-product-version
@ -95,9 +100,11 @@ jobs:
- name: Build UI - name: Build UI
run: | run: |
CONSUL_VERSION=${{ needs.get-product-version.outputs.product-version }} CONSUL_VERSION=${{ needs.get-product-version.outputs.product-version }}
CONSUL_DATE=${{ needs.get-product-version.outputs.product-date }}
CONSUL_BINARY_TYPE=${CONSUL_BINARY_TYPE} CONSUL_BINARY_TYPE=${CONSUL_BINARY_TYPE}
CONSUL_COPYRIGHT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD) CONSUL_COPYRIGHT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD)
echo "consul_version is ${CONSUL_VERSION}" echo "consul_version is ${CONSUL_VERSION}"
echo "consul_date is ${CONSUL_DATE}"
echo "consul binary type is ${CONSUL_BINARY_TYPE}" echo "consul binary type is ${CONSUL_BINARY_TYPE}"
echo "consul copyright year is ${CONSUL_COPYRIGHT_YEAR}" echo "consul copyright year is ${CONSUL_COPYRIGHT_YEAR}"
cd ui && make && cd .. cd ui && make && cd ..

View File

@ -25,7 +25,9 @@ GIT_COMMIT?=$(shell git rev-parse --short HEAD)
GIT_COMMIT_YEAR?=$(shell git show -s --format=%cd --date=format:%Y HEAD) GIT_COMMIT_YEAR?=$(shell git show -s --format=%cd --date=format:%Y HEAD)
GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
GIT_IMPORT=github.com/hashicorp/consul/version GIT_IMPORT=github.com/hashicorp/consul/version
GOLDFLAGS=-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY) DATE_FORMAT="%Y-%m-%dT%H:%M:%SZ" # it's tricky to do an RFC3339 format in a cross platform way, so we hardcode UTC
GIT_DATE=$(shell $(CURDIR)/build-support/scripts/build-date.sh) # we're using this for build date because it's stable across platform builds
GOLDFLAGS=-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY) -X $(GIT_IMPORT).BuildDate=$(GIT_DATE)
ifeq ($(FORCE_REBUILD),1) ifeq ($(FORCE_REBUILD),1)
NOCACHE=--no-cache NOCACHE=--no-cache

View File

@ -91,6 +91,7 @@ func (s *HTTPHandlers) AgentSelf(resp http.ResponseWriter, req *http.Request) (i
Revision string Revision string
Server bool Server bool
Version string Version string
BuildDate string
}{ }{
Datacenter: s.agent.config.Datacenter, Datacenter: s.agent.config.Datacenter,
PrimaryDatacenter: s.agent.config.PrimaryDatacenter, PrimaryDatacenter: s.agent.config.PrimaryDatacenter,
@ -101,7 +102,9 @@ func (s *HTTPHandlers) AgentSelf(resp http.ResponseWriter, req *http.Request) (i
Server: s.agent.config.ServerMode, Server: s.agent.config.ServerMode,
// We expect the ent version to be part of the reported version string, and that's now part of the metadata, not the actual version. // We expect the ent version to be part of the reported version string, and that's now part of the metadata, not the actual version.
Version: s.agent.config.VersionWithMetadata(), Version: s.agent.config.VersionWithMetadata(),
BuildDate: s.agent.config.BuildDate.Format(time.RFC3339),
} }
return Self{ return Self{
Config: config, Config: config,
DebugConfig: s.agent.config.Sanitized(), DebugConfig: s.agent.config.Sanitized(),

View File

@ -804,6 +804,8 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
Version: stringVal(c.Version), Version: stringVal(c.Version),
VersionPrerelease: stringVal(c.VersionPrerelease), VersionPrerelease: stringVal(c.VersionPrerelease),
VersionMetadata: stringVal(c.VersionMetadata), VersionMetadata: stringVal(c.VersionMetadata),
// What is a sensible default for BuildDate?
BuildDate: timeValWithDefault(c.BuildDate, time.Date(1970, 1, 00, 00, 00, 01, 0, time.UTC)),
// consul configuration // consul configuration
ConsulCoordinateUpdateBatchSize: intVal(c.Consul.Coordinate.UpdateBatchSize), ConsulCoordinateUpdateBatchSize: intVal(c.Consul.Coordinate.UpdateBatchSize),
@ -1946,6 +1948,13 @@ func stringVal(v *string) string {
return *v return *v
} }
func timeValWithDefault(v *time.Time, defaultVal time.Time) time.Time {
if v == nil {
return defaultVal
}
return *v
}
func float64ValWithDefault(v *float64, defaultVal float64) float64 { func float64ValWithDefault(v *float64, defaultVal float64) float64 {
if v == nil { if v == nil {
return defaultVal return defaultVal

View File

@ -3,6 +3,7 @@ package config
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
"github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/consul"
@ -273,6 +274,7 @@ type Config struct {
Version *string `mapstructure:"version"` Version *string `mapstructure:"version"`
VersionPrerelease *string `mapstructure:"version_prerelease"` VersionPrerelease *string `mapstructure:"version_prerelease"`
VersionMetadata *string `mapstructure:"version_metadata"` VersionMetadata *string `mapstructure:"version_metadata"`
BuildDate *time.Time `mapstructure:"build_date"`
// Enterprise Only // Enterprise Only
Audit Audit `mapstructure:"audit"` Audit Audit `mapstructure:"audit"`

View File

@ -2,6 +2,7 @@ package config
import ( import (
"strconv" "strconv"
"time"
"github.com/hashicorp/raft" "github.com/hashicorp/raft"
@ -210,7 +211,7 @@ func NonUserSource() Source {
// versionSource creates a config source for the version parameters. // versionSource creates a config source for the version parameters.
// This should be merged in the tail since these values are not // This should be merged in the tail since these values are not
// user configurable. // user configurable.
func versionSource(rev, ver, verPre, meta string) Source { func versionSource(rev, ver, verPre, meta string, buildDate time.Time) Source {
return LiteralSource{ return LiteralSource{
Name: "version", Name: "version",
Config: Config{ Config: Config{
@ -218,6 +219,7 @@ func versionSource(rev, ver, verPre, meta string) Source {
Version: &ver, Version: &ver,
VersionPrerelease: &verPre, VersionPrerelease: &verPre,
VersionMetadata: &meta, VersionMetadata: &meta,
BuildDate: &buildDate,
}, },
} }
} }
@ -225,7 +227,8 @@ func versionSource(rev, ver, verPre, meta string) Source {
// defaultVersionSource returns the version config source for the embedded // defaultVersionSource returns the version config source for the embedded
// version numbers. // version numbers.
func defaultVersionSource() Source { func defaultVersionSource() Source {
return versionSource(version.GitCommit, version.Version, version.VersionPrerelease, version.VersionMetadata) buildDate, _ := time.Parse(time.RFC3339, version.BuildDate) // This has been checked elsewhere
return versionSource(version.GitCommit, version.Version, version.VersionPrerelease, version.VersionMetadata, buildDate)
} }
// DefaultConsulSource returns the default configuration for the consul agent. // DefaultConsulSource returns the default configuration for the consul agent.

View File

@ -62,6 +62,7 @@ type RuntimeConfig struct {
Version string Version string
VersionPrerelease string VersionPrerelease string
VersionMetadata string VersionMetadata string
BuildDate time.Time
// consul config // consul config
ConsulCoordinateUpdateMaxBatches int ConsulCoordinateUpdateMaxBatches int
@ -1700,6 +1701,10 @@ func sanitize(name string, v reflect.Value) reflect.Value {
x := v.Interface().(time.Duration) x := v.Interface().(time.Duration)
return reflect.ValueOf(x.String()) return reflect.ValueOf(x.String())
case isTime(typ):
x := v.Interface().(time.Time)
return reflect.ValueOf(x.String())
case isString(typ): case isString(typ):
if strings.HasPrefix(name, "RetryJoinLAN[") || strings.HasPrefix(name, "RetryJoinWAN[") { if strings.HasPrefix(name, "RetryJoinLAN[") || strings.HasPrefix(name, "RetryJoinWAN[") {
x := v.Interface().(string) x := v.Interface().(string)
@ -1771,6 +1776,7 @@ func sanitize(name string, v reflect.Value) reflect.Value {
} }
func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) } func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) }
func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) }
func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map } func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map }
func isNetAddr(t reflect.Type) bool { return t.Implements(reflect.TypeOf((*net.Addr)(nil)).Elem()) } func isNetAddr(t reflect.Type) bool { return t.Implements(reflect.TypeOf((*net.Addr)(nil)).Elem()) }
func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr } func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr }

View File

@ -5661,6 +5661,7 @@ func TestLoad_FullConfig(t *testing.T) {
Version: "R909Hblt", Version: "R909Hblt",
VersionPrerelease: "ZT1JOQLn", VersionPrerelease: "ZT1JOQLn",
VersionMetadata: "GtTCa13", VersionMetadata: "GtTCa13",
BuildDate: time.Date(2019, 11, 20, 5, 0, 0, 0, time.UTC),
// consul configuration // consul configuration
ConsulCoordinateUpdateBatchSize: 128, ConsulCoordinateUpdateBatchSize: 128,
@ -6447,7 +6448,8 @@ func TestLoad_FullConfig(t *testing.T) {
ConfigFiles: []string{"testdata/full-config." + format}, ConfigFiles: []string{"testdata/full-config." + format},
HCL: []string{fmt.Sprintf(`data_dir = "%s"`, dataDir)}, HCL: []string{fmt.Sprintf(`data_dir = "%s"`, dataDir)},
} }
opts.Overrides = append(opts.Overrides, versionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn", "GtTCa13")) opts.Overrides = append(opts.Overrides, versionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn", "GtTCa13",
time.Date(2019, 11, 20, 5, 0, 0, 0, time.UTC)))
r, err := Load(opts) r, err := Load(opts)
require.NoError(t, err) require.NoError(t, err)
prototest.AssertDeepEqual(t, expected, r.RuntimeConfig) prototest.AssertDeepEqual(t, expected, r.RuntimeConfig)
@ -6641,6 +6643,7 @@ func parseCIDR(t *testing.T, cidr string) *net.IPNet {
func TestRuntimeConfig_Sanitize(t *testing.T) { func TestRuntimeConfig_Sanitize(t *testing.T) {
rt := RuntimeConfig{ rt := RuntimeConfig{
BindAddr: &net.IPAddr{IP: net.ParseIP("127.0.0.1")}, BindAddr: &net.IPAddr{IP: net.ParseIP("127.0.0.1")},
BuildDate: time.Date(2019, 11, 20, 5, 0, 0, 0, time.UTC),
CheckOutputMaxSize: checks.DefaultBufSize, CheckOutputMaxSize: checks.DefaultBufSize,
SerfAdvertiseAddrLAN: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678}, SerfAdvertiseAddrLAN: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
DNSAddrs: []net.Addr{ DNSAddrs: []net.Addr{

View File

@ -76,6 +76,7 @@
"BindAddr": "127.0.0.1", "BindAddr": "127.0.0.1",
"Bootstrap": false, "Bootstrap": false,
"BootstrapExpect": 0, "BootstrapExpect": 0,
"BuildDate": "2019-11-20 05:00:00 +0000 UTC",
"Cache": { "Cache": {
"EntryFetchMaxBurst": 42, "EntryFetchMaxBurst": 42,
"EntryFetchRate": 0.334, "EntryFetchRate": 0.334,

View File

@ -274,6 +274,42 @@ function git_branch {
return ${ret} return ${ret}
} }
function git_date {
# Arguments:
# $1 - Path to the git repo (optional - assumes pwd is git repo otherwise)
#
# Returns:
# 0 - success
# * - failure
#
# Notes:
# Echos the date of the last git commit in
local gdir="$(pwd)"
if test -d "$1"
then
gdir="$1"
fi
pushd "${gdir}" > /dev/null
local ret=0
# it's tricky to do an RFC3339 format in a cross platform way, so we hardcode UTC
local date_format="%Y-%m-%dT%H:%M:%SZ"
# we're using this for build date because it's stable across platform builds
local date="$(TZ=UTC0 git show -s --format=%cd --date=format-local:"$date_format" HEAD)" || ret=1
##local head="$(git status -b --porcelain=v2 | awk '{if ($1 == "#" && $2 =="branch.head") { print $3 }}')" || ret=1
popd > /dev/null
test ${ret} -eq 0 && echo "$date"
return ${ret}
}
function is_git_clean { function is_git_clean {
# Arguments: # Arguments:
# $1 - Path to git repo # $1 - Path to git repo
@ -325,7 +361,8 @@ function update_git_env {
export GIT_COMMIT=$(git rev-parse --short HEAD) export GIT_COMMIT=$(git rev-parse --short HEAD)
export GIT_DIRTY=$(test -n "$(git status --porcelain)" && echo "+CHANGES") export GIT_DIRTY=$(test -n "$(git status --porcelain)" && echo "+CHANGES")
export GIT_IMPORT=github.com/hashicorp/consul/version export GIT_IMPORT=github.com/hashicorp/consul/version
export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY}" export GIT_DATE=$(git_date "$1")
export GOLDFLAGS="-X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X ${T}.BuildDate=${GIT_DATE}"
return 0 return 0
} }

View File

@ -0,0 +1,72 @@
#!/bin/bash
readonly SCRIPT_NAME="$(basename ${BASH_SOURCE[0]})"
readonly SCRIPT_DIR="$(dirname ${BASH_SOURCE[0]})"
readonly SOURCE_DIR="$(dirname "$(dirname "${SCRIPT_DIR}")")"
readonly FN_DIR="$(dirname "${SCRIPT_DIR}")/functions"
source "${SCRIPT_DIR}/functions.sh"
function usage {
cat <<-EOF
Usage: ${SCRIPT_NAME} [<options ...>]
Description:
This script uses the date of the last checkin on the branch as the build date. This
is to make the date consistent across the various platforms we build on, even if they
start at different times. In practice this is the commit where the version string is set.
Options:
-s | --source DIR Path to source to build.
Defaults to "${SOURCE_DIR}"
EOF
}
function err_usage {
err "$1"
err ""
err "$(usage)"
}
function main {
declare sdir="${SOURCE_DIR}"
declare -i date=0
while test $# -gt 0
do
case "$1" in
-h | --help )
usage
return 0
;;
-s | --source )
if test -z "$2"
then
err_usage "ERROR: option -s/--source requires an argument"
return 1
fi
if ! test -d "$2"
then
err_usage "ERROR: '$2' is not a directory and not suitable for the value of -s/--source"
return 1
fi
sdir="$2"
shift 2
;;
*)
err_usage "ERROR: Unknown argument: '$1'"
return 1
;;
esac
done
git_date "${sdir}" || return 1
return 0
}
main "$@"
exit $?

View File

@ -27,12 +27,19 @@ import (
) )
func New(ui cli.Ui) *cmd { func New(ui cli.Ui) *cmd {
buildDate, err := time.Parse(time.RFC3339, consulversion.BuildDate)
if err != nil {
ui.Error(fmt.Sprintf("Fatal error with internal time set; check makefile for build date %v %v \n", buildDate, err))
return nil
}
c := &cmd{ c := &cmd{
ui: ui, ui: ui,
revision: consulversion.GitCommit, revision: consulversion.GitCommit,
version: consulversion.Version, version: consulversion.Version,
versionPrerelease: consulversion.VersionPrerelease, versionPrerelease: consulversion.VersionPrerelease,
versionHuman: consulversion.GetHumanVersion(), versionHuman: consulversion.GetHumanVersion(),
buildDate: buildDate,
flags: flag.NewFlagSet("", flag.ContinueOnError), flags: flag.NewFlagSet("", flag.ContinueOnError),
} }
config.AddFlags(c.flags, &c.configLoadOpts) config.AddFlags(c.flags, &c.configLoadOpts)
@ -53,6 +60,7 @@ type cmd struct {
version string version string
versionPrerelease string versionPrerelease string
versionHuman string versionHuman string
buildDate time.Time
configLoadOpts config.LoadOpts configLoadOpts config.LoadOpts
logger hclog.InterceptLogger logger hclog.InterceptLogger
} }
@ -194,6 +202,10 @@ func (c *cmd) run(args []string) int {
segment = "<all>" segment = "<all>"
} }
ui.Info(fmt.Sprintf(" Version: '%s'", c.versionHuman)) ui.Info(fmt.Sprintf(" Version: '%s'", c.versionHuman))
if strings.Contains(c.versionHuman, "dev") {
ui.Info(fmt.Sprintf(" Revision: '%s'", c.revision))
}
ui.Info(fmt.Sprintf(" Build Date: '%s'", c.buildDate))
ui.Info(fmt.Sprintf(" Node ID: '%s'", config.NodeID)) ui.Info(fmt.Sprintf(" Node ID: '%s'", config.NodeID))
ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName)) ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName))
if ap := config.PartitionOrEmpty(); ap != "" { if ap := config.PartitionOrEmpty(); ap != "" {

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
) )
const ( const (
@ -43,6 +44,8 @@ func (_ *prettyFormatter) Format(info *VersionInfo) (string, error) {
buffer.WriteString(fmt.Sprintf("Revision %s\n", info.Revision)) buffer.WriteString(fmt.Sprintf("Revision %s\n", info.Revision))
} }
buffer.WriteString(fmt.Sprintf("Build Date %s\n", info.BuildDate.Format(time.RFC3339)))
var supplement string var supplement string
if info.RPC.Default < info.RPC.Max { if info.RPC.Default < info.RPC.Max {
supplement = fmt.Sprintf(" (agent will automatically use protocol >%d when speaking to compatible agents)", supplement = fmt.Sprintf(" (agent will automatically use protocol >%d when speaking to compatible agents)",

View File

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -31,11 +32,13 @@ func golden(t *testing.T, name, got string) string {
} }
func TestFormat(t *testing.T) { func TestFormat(t *testing.T) {
buildDate, _ := time.Parse(time.RFC3339, "2022-06-01T13:18:45Z")
info := VersionInfo{ info := VersionInfo{
HumanVersion: "1.99.3-beta1", HumanVersion: "1.99.3-beta1",
Version: "1.99.3", Version: "1.99.3",
Prerelease: "beta1", Prerelease: "beta1",
Revision: "5e5dbedd47a5f875b60e241c5555a9caab595246", Revision: "5e5dbedd47a5f875b60e241c5555a9caab595246",
BuildDate: buildDate,
RPC: RPCVersionInfo{ RPC: RPCVersionInfo{
Default: 2, Default: 2,
Min: 1, Min: 1,

View File

@ -2,6 +2,7 @@
"Version": "1.99.3", "Version": "1.99.3",
"Revision": "5e5dbedd47a5f875b60e241c5555a9caab595246", "Revision": "5e5dbedd47a5f875b60e241c5555a9caab595246",
"Prerelease": "beta1", "Prerelease": "beta1",
"BuildDate": "2022-06-01T13:18:45Z",
"RPC": { "RPC": {
"Default": 2, "Default": 2,
"Min": 1, "Min": 1,

View File

@ -1,3 +1,4 @@
Consul v1.99.3-beta1 Consul v1.99.3-beta1
Revision 5e5dbedd47a5f875b60e241c5555a9caab595246 Revision 5e5dbedd47a5f875b60e241c5555a9caab595246
Build Date 2022-06-01T13:18:45Z
Protocol 2 spoken by default, understands 1 to 3 (agent will automatically use protocol >2 when speaking to compatible agents) Protocol 2 spoken by default, understands 1 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

View File

@ -4,6 +4,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/command/flags" "github.com/hashicorp/consul/command/flags"
@ -46,6 +47,7 @@ type VersionInfo struct {
Version string Version string
Revision string Revision string
Prerelease string Prerelease string
BuildDate time.Time
RPC RPCVersionInfo RPC RPCVersionInfo
} }
@ -59,11 +61,20 @@ func (c *cmd) Run(args []string) int {
c.UI.Error(err.Error()) c.UI.Error(err.Error())
return 1 return 1
} }
// We parse this here because consul version is used in our 'smoke' tests and we want to fail early
buildDate, err := time.Parse(time.RFC3339, version.BuildDate)
if err != nil {
c.UI.Error(err.Error())
return 1
}
out, err := formatter.Format(&VersionInfo{ out, err := formatter.Format(&VersionInfo{
HumanVersion: version.GetHumanVersion(), HumanVersion: version.GetHumanVersion(),
Version: version.Version, Version: version.Version,
Revision: version.GitCommit, Revision: version.GitCommit,
Prerelease: version.VersionPrerelease, Prerelease: version.VersionPrerelease,
BuildDate: buildDate,
RPC: RPCVersionInfo{ RPC: RPCVersionInfo{
Default: consul.DefaultRPCProtocol, Default: consul.DefaultRPCProtocol,
Min: int(consul.ProtocolVersionMin), Min: int(consul.ProtocolVersionMin),

View File

@ -23,6 +23,10 @@ var (
// then it means that it is a final release. Otherwise, this is a pre-release // then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc. // such as "dev" (in development), "beta", "rc1", etc.
VersionPrerelease = "dev" VersionPrerelease = "dev"
// The date/time of the build (actually the HEAD commit in git, to preserve stability)
// This isn't just informational, but is also used by the licensing system. Default is chosen to be flagantly wrong.
BuildDate string = "1970-01-01T00:00:01Z"
) )
// GetHumanVersion composes the parts of the version in a way that's suitable // GetHumanVersion composes the parts of the version in a way that's suitable