fetch latest version tag from docker hub (#11362)
closes #11352 --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -1,6 +1,8 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||
import {
|
||||
AuthException,
|
||||
@ -276,4 +278,102 @@ describe('AdminPanelService', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getVersionInfo', () => {
|
||||
const mockEnvironmentGet = jest.fn();
|
||||
const mockAxiosGet = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockEnvironmentGet.mockReset();
|
||||
mockAxiosGet.mockReset();
|
||||
jest.spyOn(axios, 'get').mockImplementation(mockAxiosGet);
|
||||
service['environmentService'].get = mockEnvironmentGet;
|
||||
});
|
||||
|
||||
it('should return current and latest version when everything works', async () => {
|
||||
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||
mockAxiosGet.mockResolvedValue({
|
||||
data: {
|
||||
results: [
|
||||
{ name: '2.0.0' },
|
||||
{ name: '1.5.0' },
|
||||
{ name: '1.0.0' },
|
||||
{ name: 'latest' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.getVersionInfo();
|
||||
|
||||
expect(result).toEqual({
|
||||
currentVersion: '1.0.0',
|
||||
latestVersion: '2.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle undefined APP_VERSION', async () => {
|
||||
mockEnvironmentGet.mockReturnValue(undefined);
|
||||
mockAxiosGet.mockResolvedValue({
|
||||
data: {
|
||||
results: [{ name: '2.0.0' }, { name: 'latest' }],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.getVersionInfo();
|
||||
|
||||
expect(result).toEqual({
|
||||
currentVersion: undefined,
|
||||
latestVersion: '2.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle Docker Hub API error', async () => {
|
||||
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||
mockAxiosGet.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
const result = await service.getVersionInfo();
|
||||
|
||||
expect(result).toEqual({
|
||||
currentVersion: '1.0.0',
|
||||
latestVersion: 'latest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty Docker Hub tags', async () => {
|
||||
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||
mockAxiosGet.mockResolvedValue({
|
||||
data: {
|
||||
results: [],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.getVersionInfo();
|
||||
|
||||
expect(result).toEqual({
|
||||
currentVersion: '1.0.0',
|
||||
latestVersion: 'latest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle invalid semver tags', async () => {
|
||||
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||
mockAxiosGet.mockResolvedValue({
|
||||
data: {
|
||||
results: [
|
||||
{ name: '2.0.0' },
|
||||
{ name: 'invalid-version' },
|
||||
{ name: 'latest' },
|
||||
{ name: '1.0.0' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.getVersionInfo();
|
||||
|
||||
expect(result).toEqual({
|
||||
currentVersion: '1.0.0',
|
||||
latestVersion: '2.0.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -10,6 +10,7 @@ import { SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-he
|
||||
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
|
||||
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
|
||||
import { VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
|
||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||
import { FeatureFlagException } from 'src/engine/core-modules/feature-flag/feature-flag.exception';
|
||||
@ -114,4 +115,10 @@ export class AdminPanelResolver {
|
||||
timeRange,
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Query(() => VersionInfo)
|
||||
async versionInfo(): Promise<VersionInfo> {
|
||||
return this.adminService.getVersionInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import axios from 'axios';
|
||||
import semver from 'semver';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { EnvironmentVariable } from 'src/engine/core-modules/admin-panel/dtos/environment-variable.dto';
|
||||
import { EnvironmentVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/environment-variables-group.dto';
|
||||
import { EnvironmentVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/environment-variables.output';
|
||||
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||
import { VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
@ -163,4 +166,30 @@ export class AdminPanelService {
|
||||
|
||||
return { groups };
|
||||
}
|
||||
|
||||
async getVersionInfo(): Promise<VersionInfo> {
|
||||
const currentVersion = this.environmentService.get('APP_VERSION');
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
'https://hub.docker.com/v2/repositories/twentycrm/twenty/tags?page_size=100',
|
||||
);
|
||||
|
||||
const versions = response.data.results
|
||||
.filter((tag) => tag && tag.name !== 'latest')
|
||||
.map((tag) => semver.coerce(tag.name)?.version)
|
||||
.filter((version) => version !== undefined);
|
||||
|
||||
if (versions.length === 0) {
|
||||
return { currentVersion, latestVersion: 'latest' };
|
||||
}
|
||||
|
||||
versions.sort((a, b) => semver.compare(b, a));
|
||||
const latestVersion = versions[0];
|
||||
|
||||
return { currentVersion, latestVersion };
|
||||
} catch (error) {
|
||||
return { currentVersion, latestVersion: 'latest' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class VersionInfo {
|
||||
@Field(() => String, { nullable: true })
|
||||
currentVersion?: string;
|
||||
|
||||
@Field(() => String)
|
||||
latestVersion: string;
|
||||
}
|
||||
Reference in New Issue
Block a user