ApiServerClient: Avoid special characters in generateName (#107331)
This commit is contained in:
@@ -1,12 +1,23 @@
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
|
||||
import { DatasourceAPIVersions } from './client';
|
||||
import { DatasourceAPIVersions, ScopedResourceClient } from './client';
|
||||
import { GroupVersionResource } from './types';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getBackendSrv: jest.fn().mockReturnValue({
|
||||
get: jest.fn(),
|
||||
post: jest.fn(),
|
||||
}),
|
||||
config: {},
|
||||
config: {
|
||||
buildInfo: { versionString: 'test-version' },
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('app/core/services/context_srv');
|
||||
|
||||
jest.mock('../../api/utils', () => ({
|
||||
getAPINamespace: jest.fn().mockReturnValue('default'),
|
||||
}));
|
||||
|
||||
describe('DatasourceAPIVersions', () => {
|
||||
@@ -33,3 +44,213 @@ describe('DatasourceAPIVersions', () => {
|
||||
expect(getMock).toHaveBeenCalledWith('/apis');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ScopedResourceClient', () => {
|
||||
let client: ScopedResourceClient;
|
||||
let postMock: jest.Mock;
|
||||
const gvr: GroupVersionResource = { group: 'test.grafana.app', version: 'v1', resource: 'testresources' };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
postMock = jest.fn().mockResolvedValue({ metadata: { name: 'created-resource' } });
|
||||
(getBackendSrv as jest.Mock).mockReturnValue({
|
||||
post: postMock,
|
||||
});
|
||||
client = new ScopedResourceClient(gvr);
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should generate name prefix from user login with alphabetic characters', async () => {
|
||||
contextSrv.user.login = 'john.doe123';
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'jo',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle login with special characters and numbers', async () => {
|
||||
contextSrv.user.login = 'user@example.com';
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'us',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle login starting with numbers', async () => {
|
||||
contextSrv.user.login = '123admin456';
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'ad',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle login with only one alphabetic character', async () => {
|
||||
contextSrv.user.login = '123a456';
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'a',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle login with no alphabetic characters', async () => {
|
||||
contextSrv.user.login = '12345@#$%';
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'g',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to "g" when no login exists', async () => {
|
||||
// @ts-expect-error - we want to test the fallback behavior
|
||||
contextSrv.user.login = null;
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'g',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to "g" when login is undefined', async () => {
|
||||
// @ts-expect-error - we want to test the fallback behavior
|
||||
contextSrv.user.login = undefined;
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'g',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to "g" when login is empty string', async () => {
|
||||
contextSrv.user.login = '';
|
||||
|
||||
const obj = { metadata: {}, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'g',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set generateName when metadata.name is already provided', async () => {
|
||||
contextSrv.user.login = 'testuser';
|
||||
|
||||
const obj = { metadata: { name: 'existing-name' }, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
name: 'existing-name',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
expect(postMock.mock.calls[0][1].metadata).not.toHaveProperty('generateName');
|
||||
});
|
||||
|
||||
it('should not set generateName when metadata.generateName is already provided', async () => {
|
||||
contextSrv.user.login = 'testuser';
|
||||
|
||||
const obj = { metadata: { generateName: 'existing-prefix' }, spec: {} };
|
||||
await client.create(obj);
|
||||
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
'/apis/test.grafana.app/v1/namespaces/default/testresources',
|
||||
{
|
||||
metadata: {
|
||||
generateName: 'existing-prefix',
|
||||
annotations: { 'grafana.app/saved-from-ui': 'test-version' },
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
{ params: undefined }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,9 +113,10 @@ export class ScopedResourceClient<T = object, S = object, K = string> implements
|
||||
public async create(obj: ResourceForCreate<T, K>, params?: ResourceClientWriteParams): Promise<Resource<T, S, K>> {
|
||||
if (!obj.metadata.name && !obj.metadata.generateName) {
|
||||
const login = contextSrv.user.login;
|
||||
// GenerateName lets the apiserver create a new uid for the name
|
||||
// THe passed in value is the suggested prefix
|
||||
obj.metadata.generateName = login ? login.slice(0, 2) : 'g';
|
||||
// GenerateName lets the apiserver create a unique name by appending random characters to the prefix.
|
||||
// This strips out special characters, numbers, and symbols to ensure a valid prefix.
|
||||
const alphabeticChars = login ? login.replace(/[^a-zA-Z]/g, '').slice(0, 2) : '';
|
||||
obj.metadata.generateName = alphabeticChars || 'g';
|
||||
}
|
||||
setSavedFromUIAnnotation(obj.metadata);
|
||||
return getBackendSrv().post(this.url, obj, {
|
||||
|
||||
Reference in New Issue
Block a user