Skip to content

Commit

Permalink
Connect My Computer: Keeping compatibility promise (#31951) (#32394)
Browse files Browse the repository at this point in the history
* Add server version to cluster

* Add components to display compatibility promise

* Show compatibility promise on status page

* Show compatibility promise on setup page

* Rename `serverVersion` -> `proxyVersion`, make all places to use `makeRootCluster`/`makeLeafCluster`

* Move `UpgradeAgentSuggestion` to a new file, make it stateless

* Return `isAgentCompatible` instead of `isNonCompatibleAgent` from context

* Add // DELETE IN comments

* Improve copies

* Add a story for too old client in Setup

* Extract CONNECT_MY_COMPUTER_RELEASE_MAJOR_VERSION

* Run prettier

* Fix license

* Show an error on the CMC icon when the agent is not compatible

* Always say "version" before the version number

* Adjust tests

* Drop "if you wish" from the copies

(cherry picked from commit 81c352c)
  • Loading branch information
gzdunek authored Sep 22, 2023
1 parent d58010c commit a0df9bc
Show file tree
Hide file tree
Showing 26 changed files with 850 additions and 249 deletions.
218 changes: 115 additions & 103 deletions gen/proto/go/teleport/lib/teleterm/v1/cluster.pb.go

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions gen/proto/js/teleport/lib/teleterm/v1/cluster_pb.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 31 additions & 1 deletion gen/proto/js/teleport/lib/teleterm/v1/cluster_pb.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/teleterm/apiserver/handler/handler_clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func newAPIRootClusterWithDetails(cluster *clusters.ClusterWithDetails) (*api.Cl
return nil, trace.Wrap(err)
}
apiCluster.LoggedInUser.UserType = userType
apiCluster.ProxyVersion = cluster.ProxyVersion

return apiCluster, nil
}
Expand Down
26 changes: 17 additions & 9 deletions lib/teleterm/clusters/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type ClusterWithDetails struct {
ACL *api.ACL
// UserType identifies whether the user is a local user or comes from an SSO provider.
UserType types.UserType
// ProxyVersion is the cluster proxy's service version.
ProxyVersion string
}

// Connected indicates if connection to the cluster can be established
Expand All @@ -81,14 +83,19 @@ func (c *Cluster) Connected() bool {
// and enabled enterprise features. This method requires a valid cert.
func (c *Cluster) GetWithDetails(ctx context.Context) (*ClusterWithDetails, error) {
var (
pingResponse proto.PingResponse
caps *types.AccessCapabilities
authClusterID string
acl *api.ACL
user types.User
authPingResponse proto.PingResponse
caps *types.AccessCapabilities
authClusterID string
acl *api.ACL
user types.User
)

err := AddMetadataToRetryableError(ctx, func() error {
clusterPingResponse, err := c.clusterClient.Ping(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

err = AddMetadataToRetryableError(ctx, func() error {
proxyClient, err := c.clusterClient.ConnectToProxy(ctx)
if err != nil {
return trace.Wrap(err)
Expand All @@ -101,7 +108,7 @@ func (c *Cluster) GetWithDetails(ctx context.Context) (*ClusterWithDetails, erro
}
defer authClient.Close()

pingResponse, err = authClient.Ping(ctx)
authPingResponse, err = authClient.Ping(ctx)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -131,7 +138,7 @@ func (c *Cluster) GetWithDetails(ctx context.Context) (*ClusterWithDetails, erro
}

roleSet := services.NewRoleSet(roles...)
userACL := services.NewUserACL(user, roleSet, *pingResponse.ServerFeatures, false)
userACL := services.NewUserACL(user, roleSet, *authPingResponse.ServerFeatures, false)

acl = &api.ACL{
RecordedSessions: convertToAPIResourceAccess(userACL.RecordedSessions),
Expand All @@ -158,10 +165,11 @@ func (c *Cluster) GetWithDetails(ctx context.Context) (*ClusterWithDetails, erro
Cluster: c,
SuggestedReviewers: caps.SuggestedReviewers,
RequestableRoles: caps.RequestableRoles,
Features: pingResponse.ServerFeatures,
Features: authPingResponse.ServerFeatures,
AuthClusterID: authClusterID,
ACL: acl,
UserType: user.GetUserType(),
ProxyVersion: clusterPingResponse.ServerVersion,
}

return withDetails, nil
Expand Down
3 changes: 3 additions & 0 deletions proto/teleport/lib/teleterm/v1/cluster.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ message Cluster {
// during the first auth server startup.
// Only present when detailed information is queried from the auth server.
string auth_cluster_id = 9;
// ProxyVersion is the cluster proxy's service version.
// Only present when detailed information is queried from the proxy server.
string proxy_version = 10;
}

// LoggedInUser describes a logged-in user
Expand Down
15 changes: 15 additions & 0 deletions web/packages/teleterm/src/services/tshd/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ export const makeRootCluster = (
proxyHost: 'teleport-local:3080',
authClusterId: '73c4746b-d956-4f16-9848-4e3469f70762',
loggedInUser: makeLoggedInUser(),
proxyVersion: '1.0.0',
...props,
});

export const makeLeafCluster = (
props: Partial<tsh.Cluster> = {}
): tsh.Cluster => ({
uri: '/clusters/teleport-local/leaves/leaf',
name: 'teleport-local-leaf',
connected: true,
leaf: true,
proxyHost: '',
authClusterId: '',
loggedInUser: makeLoggedInUser(),
proxyVersion: '',
...props,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright 2023 Gravitational, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import { screen } from '@testing-library/react';
import { render } from 'design/utils/testing';

import { makeRootCluster } from 'teleterm/services/tshd/testHelpers';
import { makeRuntimeSettings } from 'teleterm/mainProcess/fixtures/mocks';
import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider';
import { MockWorkspaceContextProvider } from 'teleterm/ui/fixtures/MockWorkspaceContextProvider';
import { MockAppContext } from 'teleterm/ui/fixtures/mocks';

import { isAgentCompatible, CompatibilityError } from './CompatibilityPromise';

describe('isAgentCompatible', () => {
const testCases = [
{
agentVersion: '2.0.0',
proxyVersion: '2.0.0',
isCompatible: true,
},
{
agentVersion: '2.1.0',
proxyVersion: '2.0.0',
isCompatible: true,
},
{
agentVersion: '3.0.0',
proxyVersion: '2.0.0',
isCompatible: false,
},
{
agentVersion: '2.0.0',
proxyVersion: '3.0.0',
isCompatible: true,
},
{
agentVersion: '2.0.0',
proxyVersion: '4.0.0',
isCompatible: false,
},
];
test.each(testCases)(
'should agent $agentVersion and cluster $proxyVersion be compatible? $isCompatible',
({ agentVersion, proxyVersion, isCompatible }) => {
expect(
isAgentCompatible(
proxyVersion,
makeRuntimeSettings({ appVersion: agentVersion })
)
).toBe(isCompatible);
}
);
});

test('compatibilityError shows app upgrade instructions', async () => {
const agentVersion = '1.0.0';
const proxyVersion = '3.0.0';
const appContext = new MockAppContext({ appVersion: agentVersion });
const cluster = makeRootCluster({ proxyVersion });
appContext.clustersService.setState(draftState => {
draftState.clusters.set(cluster.uri, cluster);
});

render(
<MockAppContextProvider appContext={appContext}>
<MockWorkspaceContextProvider rootClusterUri={cluster.uri}>
<CompatibilityError />
</MockWorkspaceContextProvider>
</MockAppContextProvider>
);

await expect(
screen.findByText(
/The cluster is on version 3.0.0 while Teleport Connect is on version 1.0.0./
)
).resolves.toBeVisible();
await expect(
screen.findByText(/upgrade the app to 3.x.x/)
).resolves.toBeVisible();
});

test('compatibilityError shows cluster upgrade (and app downgrade) instructions', async () => {
const agentVersion = '15.0.0';
const proxyVersion = '14.0.0';
const appContext = new MockAppContext({ appVersion: agentVersion });
const cluster = makeRootCluster({ proxyVersion });
appContext.clustersService.setState(draftState => {
draftState.clusters.set(cluster.uri, cluster);
});

render(
<MockAppContextProvider appContext={appContext}>
<MockWorkspaceContextProvider rootClusterUri={cluster.uri}>
<CompatibilityError />
</MockWorkspaceContextProvider>
</MockAppContextProvider>
);

await expect(
screen.findByText(
/The cluster is on version 14.0.0 while Teleport Connect is on version 15.0.0./
)
).resolves.toBeVisible();
await expect(
screen.findByText(/downgrade the app to version 14.1.0/)
).resolves.toBeVisible();
await expect(
screen.findByText(/upgrade the cluster to version 15.x.x/)
).resolves.toBeVisible();
});
Loading

0 comments on commit a0df9bc

Please sign in to comment.