From 076cd025e230324e9e3e964e07efb39ea82a893a Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Fri, 4 Oct 2024 12:01:55 -0400 Subject: [PATCH] Expose client tools auto update for find endpoint (#46785) * Expose client tools auto update for find endpoint * Group auto update settings in find response Log error instead returning error Add tests auto update settings in find endpoint Add check for not implemented error * Add more test cases --- api/client/webclient/webclient.go | 10 ++++ lib/web/apiserver.go | 22 +++++++- lib/web/apiserver_ping_test.go | 84 +++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/api/client/webclient/webclient.go b/api/client/webclient/webclient.go index 7a30ea67ea5d..9552023c1445 100644 --- a/api/client/webclient/webclient.go +++ b/api/client/webclient/webclient.go @@ -296,6 +296,8 @@ type PingResponse struct { ServerVersion string `json:"server_version"` // MinClientVersion is the minimum client version required by the server. MinClientVersion string `json:"min_client_version"` + // AutoUpdateSettings contains the auto update settings. + AutoUpdate AutoUpdateSettings `json:"auto_update"` // ClusterName contains the name of the Teleport cluster. ClusterName string `json:"cluster_name"` @@ -329,6 +331,14 @@ type ProxySettings struct { AssistEnabled bool `json:"assist_enabled"` } +// AutoUpdateSettings contains information about the auto update requirements. +type AutoUpdateSettings struct { + // ToolsVersion defines the version of {tsh, tctl} for client auto update. + ToolsVersion string `json:"tools_version"` + // ToolsAutoUpdate enables client auto update feature. + ToolsAutoUpdate bool `json:"tools_auto_update"` +} + // KubeProxySettings is kubernetes proxy settings type KubeProxySettings struct { // Enabled is true when kubernetes proxy is enabled diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index d9faba3cfca3..b5ef2f69cf2f 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1415,12 +1415,30 @@ func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Para if err != nil { return nil, trace.Wrap(err) } - return webclient.PingResponse{ + response := webclient.PingResponse{ Proxy: *proxyConfig, ServerVersion: teleport.Version, MinClientVersion: teleport.MinClientVersion, ClusterName: h.auth.clusterName, - }, nil + } + + autoUpdateConfig, err := h.cfg.AccessPoint.GetAutoUpdateConfig(r.Context()) + // TODO(vapopov) DELETE IN v18.0.0 check of IsNotImplemented, must be backported to all latest supported versions. + if err != nil && !trace.IsNotFound(err) && !trace.IsNotImplemented(err) { + h.log.WithError(err).Error("failed to receive AutoUpdateConfig") + } else if err == nil { + response.AutoUpdate.ToolsAutoUpdate = autoUpdateConfig.GetSpec().GetToolsAutoupdate() + } + + autoUpdateVersion, err := h.cfg.AccessPoint.GetAutoUpdateVersion(r.Context()) + // TODO(vapopov) DELETE IN v18.0.0 check of IsNotImplemented, must be backported to all latest supported versions. + if err != nil && !trace.IsNotFound(err) && !trace.IsNotImplemented(err) { + h.log.WithError(err).Error("failed to receive AutoUpdateVersion") + } else if err == nil { + response.AutoUpdate.ToolsVersion = autoUpdateVersion.GetSpec().GetToolsVersion() + } + + return response, nil } func (h *Handler) pingWithConnector(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { diff --git a/lib/web/apiserver_ping_test.go b/lib/web/apiserver_ping_test.go index 330aebcacd95..19f251148705 100644 --- a/lib/web/apiserver_ping_test.go +++ b/lib/web/apiserver_ping_test.go @@ -22,12 +22,15 @@ import ( "testing" "github.com/gravitational/roundtrip" + "github.com/gravitational/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/autoupdate" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/modules" ) @@ -231,5 +234,86 @@ func TestPing_minimalAPI(t *testing.T) { require.NoError(t, resp.Body.Close()) }) } +} + +// TestPing_autoUpdateResources tests that find endpoint return correct data related to auto updates. +func TestPing_autoUpdateResources(t *testing.T) { + env := newWebPack(t, 1, func(cfg *proxyConfig) { + cfg.minimalHandler = true + }) + proxy := env.proxies[0] + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + req, err := http.NewRequest(http.MethodGet, proxy.newClient(t).Endpoint("webapi", "find"), nil) + require.NoError(t, err) + req.Host = proxy.handler.handler.cfg.ProxyPublicAddrs[0].Host() + + tests := []struct { + name string + config *autoupdatev1pb.AutoUpdateConfigSpec + version *autoupdatev1pb.AutoUpdateVersionSpec + cleanup bool + expected webclient.AutoUpdateSettings + }{ + { + name: "resources not defined", + expected: webclient.AutoUpdateSettings{}, + }, + { + name: "enable auto update", + config: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + expected: webclient.AutoUpdateSettings{ToolsAutoUpdate: true}, + cleanup: true, + }, + { + name: "set auto update version", + version: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + expected: webclient.AutoUpdateSettings{ToolsVersion: "1.2.3"}, + cleanup: true, + }, + { + name: "enable auto update and set version", + config: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + version: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + expected: webclient.AutoUpdateSettings{ToolsAutoUpdate: true, ToolsVersion: "1.2.3"}, + }, + { + name: "modify auto update config and version", + config: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: false}, + version: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "3.2.1"}, + expected: webclient.AutoUpdateSettings{ToolsAutoUpdate: false, ToolsVersion: "3.2.1"}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.config != nil { + config, err := autoupdate.NewAutoUpdateConfig(tc.config) + require.NoError(t, err) + _, err = env.server.Auth().UpsertAutoUpdateConfig(ctx, config) + require.NoError(t, err) + } + if tc.version != nil { + version, err := autoupdate.NewAutoUpdateVersion(tc.version) + require.NoError(t, err) + _, err = env.server.Auth().UpsertAutoUpdateVersion(ctx, version) + require.NoError(t, err) + } + resp, err := client.NewInsecureWebClient().Do(req) + require.NoError(t, err) + + pr := &webclient.PingResponse{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(pr)) + require.NoError(t, resp.Body.Close()) + + assert.Equal(t, tc.expected, pr.AutoUpdate) + + if tc.cleanup { + require.NotErrorIs(t, env.server.Auth().DeleteAutoUpdateConfig(ctx), &trace.NotFoundError{}) + require.NotErrorIs(t, env.server.Auth().DeleteAutoUpdateVersion(ctx), &trace.NotFoundError{}) + } + }) + } }