diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index bc35535194..13e993fc70 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -11,11 +11,13 @@ import ( "strings" "time" - "github.com/hashicorp/consul/envoyextensions/xdscommon" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" "github.com/mitchellh/hashstructure" + "github.com/hashicorp/consul/envoyextensions/xdscommon" + "github.com/hashicorp/consul/version" + "github.com/hashicorp/go-bexpr" "github.com/hashicorp/serf/coordinate" "github.com/hashicorp/serf/serf" @@ -1683,3 +1685,12 @@ func (s *HTTPHandlers) AgentHost(resp http.ResponseWriter, req *http.Request) (i return debug.CollectHostInfo(), nil } + +// AgentVersion +// +// GET /v1/agent/version +// +// Retrieves Consul version information. +func (s *HTTPHandlers) AgentVersion(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + return version.GetBuildInfo(), nil +} diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index fad7f7d7e4..66b028b7c0 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -21,7 +21,10 @@ import ( "time" "github.com/armon/go-metrics" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/version" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-uuid" "github.com/hashicorp/serf/serf" @@ -8081,6 +8084,32 @@ func TestAgent_HostBadACL(t *testing.T) { assert.Equal(t, http.StatusOK, resp.Code) } +func TestAgent_Version(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + dc1 := "dc1" + a := NewTestAgent(t, ` + primary_datacenter = "`+dc1+`" + `) + defer a.Shutdown() + + testrpc.WaitForLeader(t, a.RPC, "dc1") + req, _ := http.NewRequest("GET", "/v1/agent/version", nil) + // req.Header.Add("X-Consul-Token", "initial-management") + resp := httptest.NewRecorder() + respRaw, err := a.srv.AgentVersion(resp, req) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, resp.Code) + assert.NotNil(t, respRaw) + + obj := respRaw.(*version.BuildInfo) + assert.NotNil(t, obj.HumanVersion) +} + // Thie tests that a proxy with an ExposeConfig is returned as expected. func TestAgent_Services_ExposeConfig(t *testing.T) { if testing.Short() { diff --git a/agent/http_register.go b/agent/http_register.go index 595a83ef52..b3f0dfea3f 100644 --- a/agent/http_register.go +++ b/agent/http_register.go @@ -29,6 +29,7 @@ func init() { registerEndpoint("/v1/agent/token/", []string{"PUT"}, (*HTTPHandlers).AgentToken) registerEndpoint("/v1/agent/self", []string{"GET"}, (*HTTPHandlers).AgentSelf) registerEndpoint("/v1/agent/host", []string{"GET"}, (*HTTPHandlers).AgentHost) + registerEndpoint("/v1/agent/version", []string{"GET"}, (*HTTPHandlers).AgentVersion) registerEndpoint("/v1/agent/maintenance", []string{"PUT"}, (*HTTPHandlers).AgentNodeMaintenance) registerEndpoint("/v1/agent/reload", []string{"PUT"}, (*HTTPHandlers).AgentReload) registerEndpoint("/v1/agent/monitor", []string{"GET"}, (*HTTPHandlers).AgentMonitor) diff --git a/api/agent.go b/api/agent.go index 6dd127b335..f45929cb5b 100644 --- a/api/agent.go +++ b/api/agent.go @@ -503,6 +503,24 @@ func (a *Agent) Host() (map[string]interface{}, error) { return out, nil } +// Version is used to retrieve information about the running Consul version and build. +func (a *Agent) Version() (map[string]interface{}, error) { + r := a.c.newRequest("GET", "/v1/agent/version") + _, resp, err := a.c.doRequest(r) + if err != nil { + return nil, err + } + defer closeResponseBody(resp) + if err := requireOK(resp); err != nil { + return nil, err + } + var out map[string]interface{} + if err := decodeBody(resp, &out); err != nil { + return nil, err + } + return out, nil +} + // Metrics is used to query the agent we are speaking to for // its current internal metric data func (a *Agent) Metrics() (*MetricsInfo, error) { diff --git a/version/version.go b/version/version.go index c66eb804e8..bd2cfd57aa 100644 --- a/version/version.go +++ b/version/version.go @@ -33,6 +33,14 @@ var ( BuildDate string = "1970-01-01T00:00:01Z" ) +// BuildInfo includes all available version info for this build +type BuildInfo struct { + SHA string + BuildDate string + HumanVersion string + FIPS string +} + // GetHumanVersion composes the parts of the version in a way that's suitable // for displaying to humans. func GetHumanVersion() string { @@ -51,3 +59,13 @@ func GetHumanVersion() string { // Strip off any single quotes added by the git information. return strings.ReplaceAll(version, "'", "") } + +// GetBuildInfo returns all available version information for this build. +func GetBuildInfo() *BuildInfo { + return &BuildInfo{ + SHA: GitCommit, + BuildDate: BuildDate, + HumanVersion: GetHumanVersion(), + FIPS: GetFIPSInfo(), + } +} diff --git a/website/content/api-docs/agent/index.mdx b/website/content/api-docs/agent/index.mdx index f84bad1be0..d76cc19107 100644 --- a/website/content/api-docs/agent/index.mdx +++ b/website/content/api-docs/agent/index.mdx @@ -206,6 +206,24 @@ $ curl \ } ``` +## Retrieve version information + +This endpoint returns version information about Consul. + +| Method | Path | Produces | +| ------ | ---------------- | ------------------ | +| `GET` | `/agent/version` | `application/json` | + +The table below shows this endpoint's support for +[blocking queries](/consul/api-docs/features/blocking), +[consistency modes](/consul/api-docs/features/consistency), +[agent caching](/consul/api-docs/features/caching), and +[required ACLs](/consul/api-docs/api-structure#authentication). + +| Blocking Queries | Consistency Modes | Agent Caching | ACL Required | +| ---------------- | ----------------- | ------------- | ------------ | +| `NO` | `none` | `none` | `none` | + ## List Members This endpoint returns the members the agent sees in the cluster gossip pool. Due