-
Notifications
You must be signed in to change notification settings - Fork 247
feat(connections): warn then connecting to an end-of-life server COMPASS-9083 #6888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9925a9d
a746b23
53cf22b
3ffb260
fc6bdca
814f1dd
2c038b4
097eb5e
3c178eb
aeb5ed8
b88316c
32e0d30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import React from 'react'; | ||
| import { | ||
| css, | ||
| Banner, | ||
| Link, | ||
| spacing, | ||
| Body, | ||
| BannerVariant, | ||
| showConfirmation, | ||
| } from '@mongodb-js/compass-components'; | ||
| import { | ||
| getConnectionTitle, | ||
| type ConnectionInfo, | ||
| } from '@mongodb-js/connection-info'; | ||
|
|
||
| const modalBodyStyles = css({ | ||
| marginTop: spacing[400], | ||
| marginBottom: spacing[200], | ||
| }); | ||
|
|
||
| export function showEndOfLifeMongoDBWarningModal( | ||
| connectionInfo?: ConnectionInfo, | ||
| version?: string | ||
| ) { | ||
| return showConfirmation({ | ||
| title: 'End-of-life MongoDB Detected', | ||
| hideCancelButton: true, | ||
| description: ( | ||
| <> | ||
| <Banner variant={BannerVariant.Warning}> | ||
| {connectionInfo | ||
| ? `Server or service "${getConnectionTitle(connectionInfo)}"` | ||
| : 'This server or service'}{' '} | ||
| appears to be running a version of MongoDB that is no longer | ||
| supported. | ||
| </Banner> | ||
| <Body className={modalBodyStyles}> | ||
| Server version{version ? ` (${version})` : ''} is considered | ||
| end-of-life, consider upgrading to get the latest features and | ||
| performance improvements.{' '} | ||
| </Body> | ||
| <Link | ||
| href="https://www.mongodb.com/legal/support-policy/lifecycles" | ||
| target="_blank" | ||
| data-testid="end-of-life-warning-modal-learn-more-link" | ||
| > | ||
| Learn more from the MongoDB Lifecycle Schedules. | ||
| </Link> | ||
| </> | ||
| ), | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,9 +30,14 @@ import { adjustConnectionOptionsBeforeConnect } from '@mongodb-js/connection-for | |||||||||||||||||||||
| import mongodbBuildInfo, { getGenuineMongoDB } from 'mongodb-build-info'; | ||||||||||||||||||||||
| import EventEmitter from 'events'; | ||||||||||||||||||||||
| import { showNonGenuineMongoDBWarningModal as _showNonGenuineMongoDBWarningModal } from '../components/non-genuine-connection-modal'; | ||||||||||||||||||||||
| import { showEndOfLifeMongoDBWarningModal as _showEndOfLifeMongoDBWarningModal } from '../components/end-of-life-connection-modal'; | ||||||||||||||||||||||
| import ConnectionString from 'mongodb-connection-string-url'; | ||||||||||||||||||||||
| import type { ExtraConnectionData as ExtraConnectionDataForTelemetry } from '@mongodb-js/compass-telemetry'; | ||||||||||||||||||||||
| import { connectable } from '../utils/connection-supports'; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| getLatestEndOfLifeServerVersion, | ||||||||||||||||||||||
| isEndOfLifeVersion, | ||||||||||||||||||||||
| } from '../utils/end-of-life-server'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export type ConnectionsEventMap = { | ||||||||||||||||||||||
| connected: ( | ||||||||||||||||||||||
|
|
@@ -1818,6 +1823,28 @@ const connectWithOptions = ( | |||||||||||||||||||||
| .isGenuine === false | ||||||||||||||||||||||
| ) { | ||||||||||||||||||||||
| dispatch(showNonGenuineMongoDBWarningModal(connectionInfo.id)); | ||||||||||||||||||||||
| } else if (preferences.getPreferences().networkTraffic) { | ||||||||||||||||||||||
| void dataService | ||||||||||||||||||||||
| .instance() | ||||||||||||||||||||||
| .then(async (instance) => { | ||||||||||||||||||||||
| const { version } = instance.build; | ||||||||||||||||||||||
| const latestEndOfLifeServerVersion = | ||||||||||||||||||||||
| await getLatestEndOfLifeServerVersion(); | ||||||||||||||||||||||
| if (isEndOfLifeVersion(version, latestEndOfLifeServerVersion)) { | ||||||||||||||||||||||
| dispatch( | ||||||||||||||||||||||
| showEndOfLifeMongoDBWarningModal( | ||||||||||||||||||||||
| connectionInfo.id, | ||||||||||||||||||||||
| instance.build.version | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
| .catch((err) => { | ||||||||||||||||||||||
| debug( | ||||||||||||||||||||||
| 'failed to get instance details to determine if the server version is end-of-life', | ||||||||||||||||||||||
| err | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||
| dispatch(connectionAttemptError(connectionInfo, err)); | ||||||||||||||||||||||
|
|
@@ -2142,6 +2169,17 @@ export const showNonGenuineMongoDBWarningModal = ( | |||||||||||||||||||||
| }; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export const showEndOfLifeMongoDBWarningModal = ( | ||||||||||||||||||||||
| connectionId: string, | ||||||||||||||||||||||
| version: string | ||||||||||||||||||||||
| ): ConnectionsThunkAction<void> => { | ||||||||||||||||||||||
| return (_dispatch, getState, { track }) => { | ||||||||||||||||||||||
| const connectionInfo = getCurrentConnectionInfo(getState(), connectionId); | ||||||||||||||||||||||
| track('Screen', { name: 'end_of_life_mongodb_modal' }, connectionInfo); | ||||||||||||||||||||||
| void _showEndOfLifeMongoDBWarningModal(connectionInfo, version); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a nit, but I'd personally probably have jumped through hoops to come up with two different names where one showEndOfLifeMongoDBWarningModal is the thunk action creator thingy and one is the function from the component. But I can live with this ;)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH, I didn't pay too much attention to naming there as I was mostly copying from the code above: compass/packages/compass-connections/src/stores/connections-store-redux.ts Lines 2162 to 2171 in 32e0d30
|
||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| type ImportConnectionsFn = Required<ConnectionStorage>['importConnections']; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export const importConnections = ( | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { expect } from 'chai'; | ||
| import { isEndOfLifeVersion } from './end-of-life-server'; | ||
|
|
||
| describe('isEndOfLifeVersion', function () { | ||
| const LATEST_END_OF_LIFE_VERSION = '4.4.x'; | ||
|
|
||
| function expectVersions(versions: string[], expected: boolean) { | ||
| for (const version of versions) { | ||
| expect(isEndOfLifeVersion(version, LATEST_END_OF_LIFE_VERSION)).to.equal( | ||
| expected, | ||
| `Expected ${version} to be ${ | ||
| expected ? 'end of life' : 'not end of life' | ||
| }` | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| it('returns true for v4.4 and below', () => { | ||
| expectVersions( | ||
| ['4.4.0', '4.3.0', '4.0', '4.0-beta.0', '1.0.0', '0.0.1', '3.999.0'], | ||
| true | ||
| ); | ||
| }); | ||
|
|
||
| it('returns true for v4.5 and above', () => { | ||
| expectVersions( | ||
| ['4.5.0', '5.0.0', '5.0.25', '6.0.0', '7.0.0', '8.0.0'], | ||
| false | ||
| ); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import semverSatisfies from 'semver/functions/satisfies'; | ||
| import semverCoerce from 'semver/functions/coerce'; | ||
|
|
||
| import { createLogger } from '@mongodb-js/compass-logging'; | ||
|
|
||
| const { mongoLogId, log, debug } = createLogger('END-OF-LIFE-SERVER'); | ||
|
|
||
| const FALLBACK_END_OF_LIFE_SERVER_VERSION = '4.4'; | ||
| const { | ||
| HADRON_AUTO_UPDATE_ENDPOINT = process.env | ||
| .HADRON_AUTO_UPDATE_ENDPOINT_OVERRIDE, | ||
| } = process.env; | ||
|
|
||
| let latestEndOfLifeServerVersion: Promise<string> | null = null; | ||
|
|
||
| export async function getLatestEndOfLifeServerVersion(): Promise<string> { | ||
| if (!HADRON_AUTO_UPDATE_ENDPOINT) { | ||
| log.debug( | ||
| mongoLogId(1_001_000_352), | ||
| 'getLatestEndOfLifeServerVersion', | ||
| 'HADRON_AUTO_UPDATE_ENDPOINT is not set' | ||
| ); | ||
| return FALLBACK_END_OF_LIFE_SERVER_VERSION; | ||
| } | ||
|
|
||
| if (!latestEndOfLifeServerVersion) { | ||
| // Setting module scoped variable to avoid repeated fetches. | ||
| log.debug( | ||
| mongoLogId(1_001_000_353), | ||
| 'getLatestEndOfLifeServerVersion', | ||
| 'Fetching EOL server version' | ||
| ); | ||
| latestEndOfLifeServerVersion = fetch( | ||
| `${HADRON_AUTO_UPDATE_ENDPOINT}/api/v2/eol-server` | ||
| ) | ||
| .then(async (response) => { | ||
| if (response.ok) { | ||
| const result = await response.text(); | ||
| log.debug( | ||
| mongoLogId(1_001_000_354), | ||
| 'getLatestEndOfLifeServerVersion', | ||
| 'Got EOL server version response', | ||
| { result } | ||
| ); | ||
| return result; | ||
| } else { | ||
| // Reset the cached value to null so that we can try again next time. | ||
| latestEndOfLifeServerVersion = null; | ||
| throw new Error( | ||
| `Expected an OK response, got ${response.status} '${response.statusText}'` | ||
| ); | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| log.error( | ||
| mongoLogId(1_001_000_355), | ||
| 'getLatestEndOfLifeServerVersion', | ||
| 'Failed to fetch EOL server version', | ||
| { error } | ||
| ); | ||
| // We don't want any downstream code to fail just because we can't fetch the EOL server version. | ||
| return FALLBACK_END_OF_LIFE_SERVER_VERSION; | ||
| }); | ||
| } | ||
| // Return a cached or in-flight value | ||
| return latestEndOfLifeServerVersion; | ||
| } | ||
|
|
||
| export function isEndOfLifeVersion( | ||
| version: string, | ||
| latestEndOfLifeServerVersion: string | ||
| ) { | ||
| try { | ||
| const coercedVersion = semverCoerce(version); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to understand under which circumstances this would be falsy?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From the docs:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But.. will it throw or be falsy? You seem to be checking/accounting for both and it isn't clear to me that both can happen.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The try-catch is mostly to be extra safe that these calls won't prevent the caller from progressing - if we're unsure, we'll opt on the safe side instead of propagating errors. This also handles if The check for falsy |
||
| return coercedVersion | ||
| ? semverSatisfies(coercedVersion, `<=${latestEndOfLifeServerVersion}`) | ||
| : false; | ||
| } catch (error) { | ||
| debug('Error comparing versions', { error }); | ||
| // If the version is not a valid semver, we can't reliably determine if it's EOL | ||
| return false; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest hiding the cancel button on this as well, to keep things aligned and because canceling this modal won't stop connecting anyway.