Skip to content

Commit 7d4d7d8

Browse files
author
arthosofteq
committed
enhance provider
1 parent 0fb8674 commit 7d4d7d8

File tree

6 files changed

+221
-39
lines changed

6 files changed

+221
-39
lines changed

redisinsight/api/src/modules/database/database.service.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ describe('DatabaseService', () => {
167167
...mockDatabase,
168168
port: 6380,
169169
password: 'password',
170-
provider: 'LOCALHOST',
171170
});
172171

173172
expect(await service.update(
@@ -240,7 +239,6 @@ describe('DatabaseService', () => {
240239
port: 6380,
241240
password: 'password',
242241
connectionType: 'STANDALONE',
243-
provider: 'LOCALHOST',
244242
new: false,
245243
version: '7.0',
246244
ssh: true,

redisinsight/api/src/modules/database/database.service.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import ERROR_MESSAGES from 'src/constants/error-messages';
99
import { DatabaseRepository } from 'src/modules/database/repositories/database.repository';
1010
import { DatabaseAnalytics } from 'src/modules/database/database.analytics';
1111
import {
12-
catchRedisConnectionError, classToClass, getHostingProvider, getRedisConnectionException,
12+
catchRedisConnectionError, classToClass, getRedisConnectionException,
1313
} from 'src/utils';
1414
import { CreateDatabaseDto } from 'src/modules/database/dto/create.database.dto';
1515
import { DatabaseInfoProvider } from 'src/modules/database/providers/database-info.provider';
@@ -55,6 +55,11 @@ export class DatabaseService {
5555
'clientCert',
5656
];
5757

58+
static endpointFields: string[] = [
59+
'host',
60+
'port',
61+
];
62+
5863
constructor(
5964
private repository: DatabaseRepository,
6065
private redisClientStorage: RedisClientStorage,
@@ -69,6 +74,10 @@ export class DatabaseService {
6974
return Object.keys(omitBy(dto, isUndefined)).some((field) => this.connectionFields.includes(field));
7075
}
7176

77+
static isEndpointAffected(dto: object) {
78+
return Object.keys(omitBy(dto, isUndefined)).some((field) => this.endpointFields.includes(field));
79+
}
80+
7281
private async merge(database: Database, dto: UpdateDatabaseDto): Promise<Database> {
7382
const updatedDatabase = database;
7483
if (dto?.caCert) {
@@ -184,13 +193,12 @@ export class DatabaseService {
184193
database = await this.merge(oldDatabase, dto);
185194

186195
if (DatabaseService.isConnectionAffected(dto)) {
187-
database = await this.databaseFactory.createDatabaseModel(database);
188-
189-
// todo: investigate manual update flag
190-
if (manualUpdate) {
191-
database.provider = getHostingProvider(database.host);
196+
if (DatabaseService.isEndpointAffected(dto)) {
197+
database.provider = undefined;
192198
}
193199

200+
database = await this.databaseFactory.createDatabaseModel(database);
201+
194202
await this.redisClientStorage.removeManyByMetadata({ databaseId: id });
195203
}
196204

redisinsight/api/src/modules/database/entities/database.entity.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,22 @@ import { SshOptionsEntity } from 'src/modules/ssh/entities/ssh-options.entity';
1010
import { CloudDatabaseDetailsEntity } from 'src/modules/cloud/database/entities/cloud-database-details.entity';
1111

1212
export enum HostingProvider {
13-
UNKNOWN = 'UNKNOWN',
14-
LOCALHOST = 'LOCALHOST',
1513
RE_CLUSTER = 'RE_CLUSTER',
1614
RE_CLOUD = 'RE_CLOUD',
15+
REDIS_MANAGED = 'REDIS_MANAGED',
1716
AZURE = 'AZURE',
18-
AWS = 'AWS',
19-
GOOGLE = 'GOOGLE',
17+
COMMUNITY_EDITION = 'COMMUNITY_EDITION',
18+
AWS_ELASTICACHE = 'AWS_ELASTICACHE',
19+
AWS_MEMORYDB = 'AWS_MEMORYDB',
20+
VALKEY = 'VALKEY',
21+
MEMORYSTORE = 'MEMORYSTORE',
22+
DRAGONFLY = 'DRAGONFLY',
23+
KEYDB = 'KEYDB',
24+
GARNET = 'GARNET',
25+
KVROCKS = 'KVROCKS',
26+
REDICT = 'REDICT',
27+
UPSTASH = 'UPSTASH',
28+
UNKNOWN = 'UNKNOWN',
2029
}
2130

2231
export enum ConnectionType {

redisinsight/api/src/modules/database/providers/database.factory.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
2-
import { ConnectionType } from 'src/modules/database/entities/database.entity';
2+
import { ConnectionType, HostingProvider } from 'src/modules/database/entities/database.entity';
33
import { catchRedisConnectionError, getHostingProvider } from 'src/utils';
44
import { Database } from 'src/modules/database/models/database';
55
import { ClientContext, SessionMetadata } from 'src/common/models';
@@ -42,6 +42,10 @@ export class DatabaseFactory {
4242
{ useRetry: true },
4343
);
4444

45+
if (!HostingProvider[model.provider]) {
46+
model.provider = await getHostingProvider(client, model.host);
47+
}
48+
4549
if (await isSentinel(client)) {
4650
if (!database.sentinelMaster) {
4751
throw new Error(RedisErrorCodes.SentinelParamsRequired);
@@ -72,10 +76,6 @@ export class DatabaseFactory {
7276

7377
model.connectionType = ConnectionType.STANDALONE;
7478

75-
if (!model.provider) {
76-
model.provider = getHostingProvider(model.host);
77-
}
78-
7979
// fetch ca cert if needed to be able to connect
8080
if (model.caCert?.id) {
8181
model.caCert = await this.caCertificateService.get(model.caCert?.id);

redisinsight/api/src/utils/hosting-provider-helper.spec.ts

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,123 @@
11
import { HostingProvider } from 'src/modules/database/entities/database.entity';
2+
import { mockStandaloneRedisClient } from 'src/__mocks__';
23
import { getHostingProvider } from './hosting-provider-helper';
34

45
const getHostingProviderTests = [
5-
{ input: '127.0.0.1', output: HostingProvider.LOCALHOST },
6-
{ input: '0.0.0.0', output: HostingProvider.LOCALHOST },
7-
{ input: 'localhost', output: HostingProvider.LOCALHOST },
8-
{ input: '172.18.0.2', output: HostingProvider.LOCALHOST },
6+
{ input: '127.0.0.1', output: HostingProvider.COMMUNITY_EDITION },
7+
{ input: '0.0.0.0', output: HostingProvider.COMMUNITY_EDITION },
8+
{ input: 'localhost', output: HostingProvider.COMMUNITY_EDITION },
9+
{ input: '172.18.0.2', output: HostingProvider.COMMUNITY_EDITION },
910
{ input: '176.87.56.244', output: HostingProvider.UNKNOWN },
1011
{ input: '192.12.56.244', output: HostingProvider.UNKNOWN },
1112
{ input: '255.255.56.244', output: HostingProvider.UNKNOWN },
1213
{ input: 'redis', output: HostingProvider.UNKNOWN },
1314
{ input: 'demo-redislabs.rlrcp.com', output: HostingProvider.RE_CLOUD },
15+
{ input: 'memorydb.aws.com', output: HostingProvider.AWS_MEMORYDB },
1416
{
1517
input: 'redis-16781.c273.us-east-1-2.ec2.cloud.redislabs.com',
1618
output: HostingProvider.RE_CLOUD,
1719
},
1820
{
1921
input: 'askubuntu.mki5tz.0001.use1.cache.amazonaws.com',
20-
output: HostingProvider.AWS,
22+
output: HostingProvider.AWS_ELASTICACHE,
2123
},
2224
{ input: 'contoso5.redis.cache.windows.net', output: HostingProvider.AZURE },
2325
{ input: 'demo-redis-provider.unknown.com', output: HostingProvider.UNKNOWN },
26+
{
27+
input: 'localhost',
28+
hello: [
29+
'server', 'redis',
30+
'modules', [
31+
[
32+
'name', 'search',
33+
'path', '/enterprise-managed',
34+
],
35+
],
36+
],
37+
info: '#Server\r\n'
38+
+ 'redis_version: 7.2.0',
39+
output: HostingProvider.REDIS_MANAGED,
40+
},
41+
{
42+
input: 'localhost',
43+
hello: [
44+
'server', 'redis',
45+
'modules', [
46+
[
47+
'name', 'search',
48+
'path', 'google',
49+
],
50+
],
51+
],
52+
output: HostingProvider.MEMORYSTORE,
53+
},
54+
{
55+
input: 'localhost',
56+
info: '#Server\r\n'
57+
+ 'server_name:valkey',
58+
output: HostingProvider.VALKEY,
59+
},
60+
{
61+
input: 'localhost',
62+
info: '#Server\r\n'
63+
+ 'dragonfly_version:df-7.0.0',
64+
output: HostingProvider.DRAGONFLY,
65+
},
66+
{
67+
input: 'localhost',
68+
info: '#Server\r\n'
69+
+ 'garnet_version:gr-7.0.0',
70+
output: HostingProvider.GARNET,
71+
},
72+
{
73+
input: 'localhost',
74+
info: '#Server\r\n'
75+
+ 'kvrocks_version:kv-7.0.0',
76+
output: HostingProvider.KVROCKS,
77+
},
78+
{
79+
input: 'localhost',
80+
info: '#Server\r\n'
81+
+ 'redict_version:rd-7.0.0',
82+
output: HostingProvider.REDICT,
83+
},
84+
{
85+
input: 'localhost',
86+
info: '#Server\r\n'
87+
+ 'upstash_version:up-7.0.0',
88+
output: HostingProvider.UPSTASH,
89+
},
90+
{
91+
input: 'localhost',
92+
info: '#Server\r\n'
93+
+ 'ElastiCache:sometinhg',
94+
output: HostingProvider.AWS_ELASTICACHE,
95+
},
96+
{
97+
input: 'localhost',
98+
info: '#Server\r\n'
99+
+ 'MemoryDB:sometinhg',
100+
output: HostingProvider.AWS_MEMORYDB,
101+
},
102+
{
103+
input: 'localhost',
104+
info: '#KeyDb\r\n'
105+
+ 'some:data',
106+
output: HostingProvider.KEYDB,
107+
},
24108
];
25109

26110
describe('getHostingProvider', () => {
111+
beforeEach(() => {
112+
mockStandaloneRedisClient.sendCommand.mockReset();
113+
});
114+
27115
getHostingProviderTests.forEach((test) => {
28116
it(`should be output: ${test.output} for input: ${test.input} `, async () => {
29-
const result = getHostingProvider(test.input);
117+
mockStandaloneRedisClient.sendCommand.mockResolvedValueOnce(test.hello);
118+
mockStandaloneRedisClient.sendCommand.mockResolvedValueOnce(test.info);
119+
120+
const result = await getHostingProvider(mockStandaloneRedisClient, test.input);
30121

31122
expect(result).toEqual(test.output);
32123
});
Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,101 @@
11
import { IP_ADDRESS_REGEX, PRIVATE_IP_ADDRESS_REGEX } from 'src/constants';
22
import { HostingProvider } from 'src/modules/database/entities/database.entity';
3+
import { RedisClient } from 'src/modules/redis/client';
34

45
// Ignore LGTM [js/incomplete-url-substring-sanitization] alert.
56
// Because we do not bind potentially dangerous logic to this.
67
// We define a hosting provider for telemetry only.
7-
export const getHostingProvider = (host: string): HostingProvider => {
8-
// Tries to detect the hosting provider from the hostname.
9-
if (host === '0.0.0.0' || host === 'localhost') {
10-
return HostingProvider.LOCALHOST;
11-
}
12-
if (IP_ADDRESS_REGEX.test(host) && PRIVATE_IP_ADDRESS_REGEX.test(host)) {
13-
return HostingProvider.LOCALHOST;
14-
}
15-
if (host.endsWith('rlrcp.com') || host.endsWith('redislabs.com')) { // lgtm[js/incomplete-url-substring-sanitization]
16-
return HostingProvider.RE_CLOUD;
17-
}
18-
if (host.endsWith('cache.amazonaws.com')) { // lgtm[js/incomplete-url-substring-sanitization]
19-
return HostingProvider.AWS;
20-
}
21-
if (host.endsWith('cache.windows.net')) { // lgtm[js/incomplete-url-substring-sanitization]
22-
return HostingProvider.AZURE;
8+
export const getHostingProvider = async (client: RedisClient, databaseHost: string): Promise<HostingProvider> => {
9+
try {
10+
const host = databaseHost.toLowerCase();
11+
12+
// Tries to detect the hosting provider from the hostname.
13+
if (host.endsWith('rlrcp.com') || host.endsWith('redislabs.com')) { // lgtm[js/incomplete-url-substring-sanitization]
14+
return HostingProvider.RE_CLOUD;
15+
}
16+
if (host.endsWith('cache.amazonaws.com')) { // lgtm[js/incomplete-url-substring-sanitization]
17+
return HostingProvider.AWS_ELASTICACHE;
18+
}
19+
if (host.includes('memorydb')) {
20+
return HostingProvider.AWS_MEMORYDB;
21+
}
22+
if (host.endsWith('cache.windows.net')) { // lgtm[js/incomplete-url-substring-sanitization]
23+
return HostingProvider.AZURE;
24+
}
25+
26+
try {
27+
const hello = JSON.stringify(await client.sendCommand(
28+
['hello'],
29+
{ replyEncoding: 'utf8' },
30+
) as string[]).toLowerCase();
31+
32+
if (hello.includes('/enterprise-managed')) {
33+
return HostingProvider.REDIS_MANAGED;
34+
}
35+
36+
if (hello.includes('google')) {
37+
return HostingProvider.MEMORYSTORE;
38+
}
39+
} catch (e) {
40+
// ignore errors
41+
}
42+
43+
try {
44+
const info = (await client.sendCommand(
45+
['info'],
46+
{ replyEncoding: 'utf8' },
47+
) as string).toLowerCase();
48+
49+
if (info.includes('elasticache')) {
50+
return HostingProvider.AWS_ELASTICACHE;
51+
}
52+
53+
if (info.includes('memorydb')) {
54+
return HostingProvider.AWS_MEMORYDB;
55+
}
56+
57+
if (info.includes('keydb')) {
58+
return HostingProvider.KEYDB;
59+
}
60+
61+
if (info.includes('valkey')) {
62+
return HostingProvider.VALKEY;
63+
}
64+
65+
if (info.includes('dragonfly_version')) {
66+
return HostingProvider.DRAGONFLY;
67+
}
68+
69+
if (info.includes('garnet_version')) {
70+
return HostingProvider.GARNET;
71+
}
72+
73+
if (info.includes('kvrocks_version')) {
74+
return HostingProvider.KVROCKS;
75+
}
76+
77+
if (info.includes('redict_version')) {
78+
return HostingProvider.REDICT;
79+
}
80+
81+
if (info.includes('upstash_version')) {
82+
return HostingProvider.UPSTASH;
83+
}
84+
} catch (e) {
85+
// ignore error
86+
}
87+
88+
if (host === '0.0.0.0' || host === 'localhost' || host === '127.0.0.1') {
89+
return HostingProvider.COMMUNITY_EDITION;
90+
}
91+
92+
// todo: investigate weather we need this
93+
if (IP_ADDRESS_REGEX.test(host) && PRIVATE_IP_ADDRESS_REGEX.test(host)) {
94+
return HostingProvider.COMMUNITY_EDITION;
95+
}
96+
} catch (e) {
97+
// ignore any error
2398
}
99+
24100
return HostingProvider.UNKNOWN;
25101
};

0 commit comments

Comments
 (0)