diff --git a/Makefile b/Makefile index 91a4c1dbe..0d130e7c9 100644 --- a/Makefile +++ b/Makefile @@ -36,17 +36,13 @@ golangci-lint: $(eval GOLINT_INSTALLED := $(shell which golangci-lint)) if [ "$(GOLINT_INSTALLED)" = "" ]; then \ - sh scripts/install-golangci-lint.sh -b $(GO_PATH)/bin v1.51.0; \ + sh scripts/install-golangci-lint.sh -b $(GO_PATH)/bin v1.51.2; \ fi; .PHONY: cleangen cleangen: @rm -f $(shell find ${THIS_DIR} -name '*.gen.go' && find ${THIS_DIR} -name '*.pb.go' && find ${THIS_DIR} -name '*.pb.gw.go') -.PHONY: install-no-plugins -install-no-plugins: export SKIP_PLUGIN_BUILD=1 -install-no-plugins: install - .PHONY: dev dev: @echo "This command has changed. Please use:" @@ -65,6 +61,10 @@ build: build-ui-ifne install: export BOUNDARY_INSTALL_BINARY=1 install: build +.PHONY: install-no-plugins +install-no-plugins: export SKIP_PLUGIN_BUILD=1 +install-no-plugins: install + .PHONY: build-memprof build-memprof: BUILD_TAGS+=memprofiler build-memprof: @@ -283,7 +283,7 @@ generate-database-dumps: test-ci: export CI_BUILD=1 test-ci: CGO_ENABLED=$(CGO_ENABLED) BUILD_TAGS='$(BUILD_TAGS)' sh -c "'$(CURDIR)/scripts/build.sh'" - go test "$(TEST_PACKAGE)" -v $(TESTARGS) -json -cover -timeout 120m | tparse -follow + go test "$(TEST_PACKAGE)" -tags="$(BUILD_TAGS)" -v $(TESTARGS) -json -cover -timeout 120m | tparse -follow .PHONY: test-sql test-sql: @@ -291,7 +291,7 @@ test-sql: .PHONY: test test: - go test "$(TEST_PACKAGE)" $(TESTARGS) -json -cover -timeout $(TEST_TIMEOUT) | tparse -follow + go test "$(TEST_PACKAGE)" -tags="$(BUILD_TAGS)" $(TESTARGS) -json -cover -timeout $(TEST_TIMEOUT) | tparse -follow .PHONY: test-sdk test-sdk: diff --git a/internal/cmd/base/listener.go b/internal/cmd/base/listener.go index e7f5e77c2..38f9574d5 100644 --- a/internal/cmd/base/listener.go +++ b/internal/cmd/base/listener.go @@ -40,6 +40,7 @@ type WorkerAuthInfo struct { Description string `json:"description"` ConnectionNonce string `json:"connection_nonce"` ProxyAddress string `json:"proxy_address"` + BoundaryVersion string `json:"boundary_version"` } // Factory is the factory function to create a listener. diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 4fba57c9e..9f865a8b4 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -48,6 +48,8 @@ var ( _ cli.CommandAutocomplete = (*Command)(nil) ) +var extraSelfTerminationConditionFuncs []func(context.Context, chan struct{}) + type Command struct { *base.Server opsServer *ops.Server @@ -921,6 +923,10 @@ func (c *Command) Run(args []string) int { } } + for _, f := range extraSelfTerminationConditionFuncs { + f(c.Context, c.ServerSideShutdownCh) + } + for !errorEncountered.Load() && !shutdownCompleted.Load() { select { case <-c.ServerSideShutdownCh: diff --git a/internal/cmd/commands/server/server.go b/internal/cmd/commands/server/server.go index 1c500aafd..25eef140f 100644 --- a/internal/cmd/commands/server/server.go +++ b/internal/cmd/commands/server/server.go @@ -41,6 +41,8 @@ var ( _ cli.CommandAutocomplete = (*Command)(nil) ) +var extraSelfTerminationConditionFuncs []func(context.Context, chan struct{}) + type Command struct { *base.Server opsServer *ops.Server @@ -697,6 +699,10 @@ func (c *Command) WaitForInterrupt() int { } } + for _, f := range extraSelfTerminationConditionFuncs { + f(c.Context, c.ServerSideShutdownCh) + } + for !shutdownCompleted.Load() { select { case <-c.ServerSideShutdownCh: diff --git a/internal/cmd/commands/version/version.go b/internal/cmd/commands/version/version.go index a9f7c3020..95c0d6f6f 100644 --- a/internal/cmd/commands/version/version.go +++ b/internal/cmd/commands/version/version.go @@ -73,6 +73,9 @@ func (c *Command) Run(args []string) int { if verInfo.VersionMetadata != "" { nonAttributeMap["Metadata"] = verInfo.VersionMetadata } + if verInfo.BuildDate != "" { + nonAttributeMap["Build Date"] = verInfo.BuildDate + } maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index dde77b56a..9c37ef0b9 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -37,6 +37,8 @@ import ( "github.com/mitchellh/mapstructure" ) +var extraParsingFuncs []func(*Config) error + const ( desktopCorsOrigin = "serve://boundary" @@ -188,6 +190,9 @@ type Controller struct { // // TODO: This field is currently internal. SchedulerRunJobInterval time.Duration `hcl:"-"` + + // License is the license used by HCP builds + License string `hcl:"license"` } func (c *Controller) InitNameIfEmpty() error { @@ -854,6 +859,12 @@ func Parse(d string) (*Config, error) { } } + for _, f := range extraParsingFuncs { + if err := f(result); err != nil { + return nil, err + } + } + return result, nil } diff --git a/internal/daemon/cluster/connstate.go b/internal/daemon/cluster/connstate.go index a45ea893c..46e088453 100644 --- a/internal/daemon/cluster/connstate.go +++ b/internal/daemon/cluster/connstate.go @@ -5,6 +5,7 @@ package cluster import ( "context" + stderror "errors" "net" "github.com/fatih/structs" @@ -13,6 +14,8 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) +var errStateNotFound = stderror.New("no state attached to the connection") + // WorkerConnectionInfo contains information about a worker that gets attached // to outgoing connections to an upstream worker or the controller through the // state of a *protocol.Conn. @@ -48,11 +51,11 @@ func GetWorkerInfoFromStateMap(ctx context.Context, c net.Conn) (*WorkerConnecti var downstreamInfo WorkerConnectionInfo pc, ok := c.(stateProvider) if !ok { - return nil, errors.New(ctx, errors.InvalidParameter, op, "connection does not provide a State structpb.Struct") + return nil, errors.Wrap(ctx, errStateNotFound, op, errors.WithMsg("conn is not a state provider")) } st := pc.State() if st == nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, "no state attached to connection") + return nil, errors.Wrap(ctx, errStateNotFound, op, errors.WithMsg("no state attached to connection")) } if err := mapstructure.Decode(st.AsMap(), &downstreamInfo); err != nil { return nil, errors.Wrap(ctx, err, op) diff --git a/internal/daemon/cluster/connstate_test.go b/internal/daemon/cluster/connstate_test.go index a21d234f4..47bfd1240 100644 --- a/internal/daemon/cluster/connstate_test.go +++ b/internal/daemon/cluster/connstate_test.go @@ -43,7 +43,7 @@ func TestWorkerConnectionInfo_errors(t *testing.T) { t.Run("wrong connection type", func(t *testing.T) { c, _ := net.Pipe() got, err := GetWorkerInfoFromStateMap(context.Background(), c) - assert.ErrorContains(t, err, "connection does not provide a State structpb.Struct") + assert.ErrorContains(t, err, "conn is not a state provider") assert.Nil(t, got) }) t.Run("no state on connection", func(t *testing.T) { diff --git a/internal/daemon/cluster/tracking_listener.go b/internal/daemon/cluster/tracking_listener.go index c7ceeb0d5..94d59dc35 100644 --- a/internal/daemon/cluster/tracking_listener.go +++ b/internal/daemon/cluster/tracking_listener.go @@ -6,11 +6,13 @@ package cluster import ( "context" "crypto/tls" + stderrors "errors" "fmt" "net" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/observability/event" + "github.com/hashicorp/boundary/version" nodee "github.com/hashicorp/nodeenrollment" "github.com/hashicorp/nodeenrollment/protocol" ) @@ -65,8 +67,31 @@ func (e *trackingListener) Accept() (net.Conn, error) { case *protocol.Conn: tlsConn = c.Conn wi, err := GetWorkerInfoFromStateMap(e.ctx, c) - if err != nil || wi == nil || len(wi.WorkerId) == 0 { - event.WriteSysEvent(e.ctx, op, "did not get worker information from state map") + switch { + case err != nil && errors.Is(err, errStateNotFound) && !version.SupportsFeature(version.Binary, version.RequireVersionInWorkerInfo): + // This will cause us to break below + wi = new(WorkerConnectionInfo) + case err != nil: + // We want to check that if we are supporting metadata that it is supplied + innerErr := fmt.Errorf("error trying to get worker information from state map: %w", err) + event.WriteError(e.ctx, op, innerErr) + conn.Close() + return conn, nil + case wi == nil: + event.WriteError(e.ctx, op, stderrors.New("nil worker info in state map")) + conn.Close() + return conn, nil + case version.Get().VersionMetadata != "": + incomingVer := version.FromVersionString(wi.BoundaryVersion) + if (incomingVer == nil || incomingVer.VersionMetadata == "") && version.SupportsFeature(version.Binary, version.RequireVersionInWorkerInfo) { + err := stderrors.New("build contains version with metadata, incoming worker version is nil or has mismatched metadata") + event.WriteError(e.ctx, op, err) + conn.Close() + return conn, nil + } + } + if len(wi.WorkerId) == 0 { + event.WriteSysEvent(e.ctx, op, "did not find worker id in state map") break } workerId = wi.WorkerId @@ -75,15 +100,18 @@ func (e *trackingListener) Accept() (net.Conn, error) { tlsConn = c default: event.WriteError(e.ctx, op, fmt.Errorf("unexpected connection type: %T", c), event.WithInfo(eventingOpts...)) + conn.Close() return conn, nil } if tlsConn == nil { event.WriteError(e.ctx, op, fmt.Errorf("nil tlsConn"), event.WithInfo(eventingOpts...)) + conn.Close() return conn, nil } if len(tlsConn.ConnectionState().PeerCertificates) == 0 { event.WriteError(e.ctx, op, fmt.Errorf("tls conn without any peer certificates"), event.WithInfo(eventingOpts...)) + conn.Close() return conn, nil } @@ -93,7 +121,7 @@ func (e *trackingListener) Accept() (net.Conn, error) { } if keyId == "" { // No key id means there is nothing to track. - event.WriteError(e.ctx, op, fmt.Errorf("no key id found for connection"), event.WithInfo(eventingOpts...)) + event.WriteError(e.ctx, op, stderrors.New("no key id found for connection"), event.WithInfo(eventingOpts...)) return conn, nil } eventingOpts = append(eventingOpts, "key_id", keyId) diff --git a/internal/daemon/common/eventing_listener.go b/internal/daemon/common/eventing_listener.go deleted file mode 100644 index 011481cb2..000000000 --- a/internal/daemon/common/eventing_listener.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "context" - "crypto/tls" - "net" - - "github.com/hashicorp/boundary/internal/errors" - "github.com/hashicorp/boundary/internal/observability/event" - nodee "github.com/hashicorp/nodeenrollment" - "github.com/hashicorp/nodeenrollment/protocol" -) - -// EventingListener simply sends an event when a worker has connected -// successfully -type eventingListener struct { - ctx context.Context - baseLn net.Listener -} - -func NewEventingListener(ctx context.Context, baseLn net.Listener) (net.Listener, error) { - const op = "common.(EventingListener).Accept" - switch { - case ctx == nil: - return nil, errors.New(ctx, errors.InvalidParameter, op, "nil context") - case baseLn == nil: - return nil, errors.New(ctx, errors.InvalidParameter, op, "nil base listener") - } - return &eventingListener{ - ctx: ctx, - baseLn: baseLn, - }, nil -} - -func (e *eventingListener) Accept() (net.Conn, error) { - const op = "common.(EventingListener).Accept" - conn, err := e.baseLn.Accept() - if err != nil || conn == nil { - return conn, err - } - - var tlsConn *tls.Conn - switch c := conn.(type) { - case *tls.Conn: - tlsConn = c - case *protocol.Conn: - tlsConn = c.Conn - default: - return conn, nil - } - - if tlsConn != nil && len(tlsConn.ConnectionState().PeerCertificates) > 0 { - keyId, err := nodee.KeyIdFromPkix(tlsConn.ConnectionState().PeerCertificates[0].SubjectKeyId) - if err == nil { - event.WriteSysEvent(e.ctx, op, "worker successfully authenticated", "key_id", keyId) - } - } - - return conn, err -} - -func (e *eventingListener) Close() error { - return e.baseLn.Close() -} - -func (e *eventingListener) Addr() net.Addr { - return e.baseLn.Addr() -} diff --git a/internal/daemon/controller/handlers/targets/target_service.go b/internal/daemon/controller/handlers/targets/target_service.go index 1553b6000..4997b69c2 100644 --- a/internal/daemon/controller/handlers/targets/target_service.go +++ b/internal/daemon/controller/handlers/targets/target_service.go @@ -95,6 +95,7 @@ var ( action.List, } + validateCredentialSourcesFn = func(context.Context, subtypes.Subtype, []target.CredentialSource) error { return nil } ValidateIngressWorkerFilterFn = IngressWorkerFilterUnsupported AuthorizeSessionWorkerFilterFn = AuthorizeSessionWithWorkerFilter WorkerFilterDeprecationMessage = fmt.Sprintf("This field is deprecated. Use %s instead.", globals.EgressWorkerFilterField) @@ -721,6 +722,11 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession if t == nil { return nil, handlers.NotFoundErrorf("Target %q not found.", t.GetPublicId()) } + if len(credSources) > 0 { + if err := validateCredentialSourcesFn(ctx, t.GetType(), credSources); err != nil { + return nil, err + } + } // Instantiate some repos sessionRepo, err := s.sessionRepoFn() diff --git a/internal/daemon/controller/listeners.go b/internal/daemon/controller/listeners.go index 978a025b9..737a9f243 100644 --- a/internal/daemon/controller/listeners.go +++ b/internal/daemon/controller/listeners.go @@ -162,16 +162,10 @@ func (c *Controller) configureForCluster(ln *base.ServerListener) (func(), error if err != nil { return nil, fmt.Errorf("error instantiating node enrollment authed split listener: %w", err) } - // This wraps the normal worker connections with something that events with - // the worker ID on successful auth/connection - eventingAuthedListener, err := common.NewEventingListener(c.baseContext, nodeeAuthedListener) - if err != nil { - return nil, fmt.Errorf("%s: error creating eventing listener: %w", op, err) - } // This wraps the normal pki worker connections with a listener which adds // the worker key id of the connections to the controller's pkiConnManager. - pkiWorkerTrackingListener, err := cluster.NewTrackingListener(c.baseContext, eventingAuthedListener, c.pkiConnManager, sourcePurpose(grpcListenerPurpose)) + pkiWorkerTrackingListener, err := cluster.NewTrackingListener(c.baseContext, nodeeAuthedListener, c.pkiConnManager, sourcePurpose(grpcListenerPurpose)) if err != nil { return nil, fmt.Errorf("%s: error creating pki worker tracking listener: %w", op, err) } diff --git a/internal/daemon/controller/testing.go b/internal/daemon/controller/testing.go index 0ff6b5a93..c38813986 100644 --- a/internal/daemon/controller/testing.go +++ b/internal/daemon/controller/testing.go @@ -23,13 +23,16 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/schema" "github.com/hashicorp/boundary/internal/gen/testing/interceptor" + "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/intglobals" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/observability/event" + "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/server" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/go-hclog" @@ -134,6 +137,26 @@ func (tc *TestController) ConnectionsRepo() *session.ConnectionRepository { return repo } +func (tc *TestController) PluginHostRepo() *plugin.Repository { + repo, err := tc.c.PluginHostRepoFn() + if err != nil { + tc.t.Fatal(err) + } + return repo +} + +func (tc *TestController) VaultCredentialRepo() *vault.Repository { + repo, err := tc.c.VaultCredentialRepoFn() + if err != nil { + tc.t.Fatal(err) + } + return repo +} + +func (tc *TestController) Scheduler() *scheduler.Scheduler { + return tc.Controller().scheduler +} + func (tc *TestController) Cancel() { tc.cancel() } diff --git a/internal/daemon/controller/worker_tls_config.go b/internal/daemon/controller/worker_tls_config.go index 8646433c5..78ce99e70 100644 --- a/internal/daemon/controller/worker_tls_config.go +++ b/internal/daemon/controller/worker_tls_config.go @@ -15,7 +15,9 @@ import ( "strings" "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/boundary/internal/server" + "github.com/hashicorp/boundary/version" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "google.golang.org/protobuf/proto" ) @@ -71,6 +73,7 @@ func (c Controller) validateWorkerTls(hello *tls.ClientHelloInfo) (*tls.Config, // * Ensures that the nonce is unique to prevent replay attacks // * Returns the shared TLS configuration that is used to establish the connection func (c Controller) v1WorkerAuthConfig(protos []string) (*tls.Config, *base.WorkerAuthInfo, error) { + const op = "controller.(Controller).v1WorkerAuthConfig" var firstMatchProto string var encString string for _, p := range protos { @@ -104,6 +107,14 @@ func (c Controller) v1WorkerAuthConfig(protos []string) (*tls.Config, *base.Work if err := json.Unmarshal(marshaledInfo, info); err != nil { return nil, nil, err } + if version.Get().VersionMetadata != "" { + incomingVer := version.FromVersionString(info.BoundaryVersion) + if (incomingVer == nil || incomingVer.VersionMetadata == "") && version.SupportsFeature(version.Binary, version.RequireVersionInWorkerInfo) { + err := errors.New("build contains version with metadata, incoming worker version is nil or has mismatched metadata") + event.WriteError(c.baseContext, op, err) + return nil, nil, err + } + } // Check for replays serversRepo, err := c.ServersRepoFn() diff --git a/internal/daemon/worker/controller_connection.go b/internal/daemon/worker/controller_connection.go index abe8e99d2..0a95725ac 100644 --- a/internal/daemon/worker/controller_connection.go +++ b/internal/daemon/worker/controller_connection.go @@ -253,9 +253,10 @@ func (w *Worker) createClientConn(addr string) error { func (w *Worker) workerAuthTLSConfig(extraAlpnProtos ...string) (*tls.Config, *base.WorkerAuthInfo, error) { var err error info := &base.WorkerAuthInfo{ - Name: w.conf.RawConfig.Worker.Name, - Description: w.conf.RawConfig.Worker.Description, - ProxyAddress: w.conf.RawConfig.Worker.PublicAddr, + Name: w.conf.RawConfig.Worker.Name, + Description: w.conf.RawConfig.Worker.Description, + ProxyAddress: w.conf.RawConfig.Worker.PublicAddr, + BoundaryVersion: version.Get().VersionNumber(), } info.ConnectionNonce, err = w.nonceFn(20) diff --git a/internal/daemon/worker/listeners.go b/internal/daemon/worker/listeners.go index 66af6114f..c4572eabb 100644 --- a/internal/daemon/worker/listeners.go +++ b/internal/daemon/worker/listeners.go @@ -230,14 +230,9 @@ func (w *Worker) configureForWorker(ln *base.ServerListener, logger *log.Logger, ln.GrpcServer = downstreamServer - eventingListener, err := common.NewEventingListener(cancelCtx, nodeeAuthListener) - if err != nil { - return nil, fmt.Errorf("%s: error creating eventing listener: %w", op, err) - } - // This wraps the normal pki worker connections with a listener which adds // the worker key id of the connections to the worker's pkiConnManager. - pkiWorkerTrackingListener, err := cluster.NewTrackingListener(cancelCtx, eventingListener, w.pkiConnManager, sourcePurpose(grpcListenerPurpose)) + pkiWorkerTrackingListener, err := cluster.NewTrackingListener(cancelCtx, nodeeAuthListener, w.pkiConnManager, sourcePurpose(grpcListenerPurpose)) if err != nil { return nil, fmt.Errorf("%s: error creating pki worker tracking listener: %w", op, err) } diff --git a/scripts/build.sh b/scripts/build.sh index a4d4bd7ae..26bdec825 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -28,6 +28,15 @@ echo "==> Build tags: ${BUILD_TAGS}" GIT_COMMIT="$(git rev-parse HEAD)" GIT_DIRTY="$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)" +# Get the build date from the latest commit since it can be used across all +# builds +function build_date() { + # It's tricky to do an RFC3339 format in a cross platform way, so we hardcode UTC + : "${DATE_FORMAT:="%Y-%m-%dT%H:%M:%SZ"}" + git show --no-show-signature -s --format=%cd --date=format:"$DATE_FORMAT" HEAD +} +BUILD_DATE=$(build_date) + # If not explicitly cross-compiling, build for the current platform if [ "${GOOS}x" == "x" ]; then GOOS=$(go env GOOS) @@ -57,7 +66,7 @@ if [ "${CI_BUILD}x" != "x" ]; then exit fi -PRODUCT_VERSION=${PRODUCT_VERSION:=$(cat version/VERSION)} +BASE_PRODUCT_VERSION=${BASE_PRODUCT_VERSION:=$(cat version/VERSION)} # Declare binary paths! BINARY_NAME="boundary${BINARY_SUFFIX}" @@ -77,10 +86,11 @@ ${GO_CMD} build \ -trimpath \ -buildvcs=false \ -ldflags " - -X github.com/hashicorp/boundary/version.GitCommit=${GIT_COMMIT}${GIT_DIRTY} - -X 'github.com/hashicorp/boundary/version.Version=$PRODUCT_VERSION' + -X 'github.com/hashicorp/boundary/version.GitCommit=${GIT_COMMIT}${GIT_DIRTY}' + -X 'github.com/hashicorp/boundary/version.Version=$BASE_PRODUCT_VERSION' -X 'github.com/hashicorp/boundary/version.VersionPrerelease=$PRERELEASE_PRODUCT_VERSION' -X 'github.com/hashicorp/boundary/version.VersionMetadata=$METADATA_PRODUCT_VERSION' + -X 'github.com/hashicorp/boundary/version.BuildDate=$BUILD_DATE' " \ -o "$BIN_PATH" \ ./cmd/boundary diff --git a/version/feature_manager.go b/version/feature_manager.go index 2ea62aedf..7cde07b5a 100644 --- a/version/feature_manager.go +++ b/version/feature_manager.go @@ -19,6 +19,7 @@ const ( IncludeStatusInCli CredentialLibraryVaultSubtype UseTargetIdForHostId + RequireVersionInWorkerInfo ) var featureMap map[Feature]MetadataConstraint @@ -62,6 +63,11 @@ func init() { featureMap[UseTargetIdForHostId] = MetadataConstraint{ Constraints: mustNewConstraints("< 0.14.0"), } + // RequireVersionInWorkerInfo allows us to take action on various workers + // based on their version, e.g. to prevent incompatibilities + featureMap[RequireVersionInWorkerInfo] = MetadataConstraint{ + Constraints: mustNewConstraints(">= 0.13.0"), + } } func mustNewConstraints(v string) gvers.Constraints { diff --git a/version/version.go b/version/version.go index 0f3e830f5..d5dcad79d 100644 --- a/version/version.go +++ b/version/version.go @@ -19,6 +19,7 @@ type Info struct { Version string `json:"version,omitempty"` VersionPrerelease string `json:"version_prerelease,omitempty"` VersionMetadata string `json:"version_metadata,omitempty"` + BuildDate string `json:"build_date,omitempty"` CgoEnabled bool `json:"cgo_enabled,omitempty"` } @@ -26,6 +27,7 @@ func Get() *Info { ver := Version rel := VersionPrerelease md := VersionMetadata + bd := BuildDate if GitDescribe != "" { ver = GitDescribe } @@ -43,6 +45,7 @@ func Get() *Info { Version: ver, VersionPrerelease: rel, VersionMetadata: md, + BuildDate: bd, } } diff --git a/version/version_base.go b/version/version_base.go index 5c354cfff..cd7bdd309 100644 --- a/version/version_base.go +++ b/version/version_base.go @@ -16,8 +16,12 @@ var ( Version = "0.12.0" // VersionPrerelease is also set at compile time, similarly to Version. - VersionPrerelease = "" + VersionPrerelease string // VersionMetadata is also set at compile time. - VersionMetadata = "" + VersionMetadata string + + // BuildDate is the date of the build, which corresponds to the timestamp of + // the most recent commit + BuildDate string )