[sonarqube-backend plugin] added optional externalUrl config

Signed-off-by: Connor Younglund <younglund_connor@bah.com>
This commit is contained in:
Connor Younglund
2023-07-27 15:19:52 -04:00
parent f602c02daa
commit a24d9df875
7 changed files with 129 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-sonarqube-backend': minor
---
Added optional `externalUrl` config for setting a different frontend URL
+28
View File
@@ -138,6 +138,34 @@ sonarqube:
apiKey: abcdef0123456789abcedf0123456789ab
```
#### Example - Different frontend and backend URLs
In some instances, you might want to use one URL for the backend and another for the frontend.
This can be achieved by using the optional `externalUrl` property in the config.
##### Single instance config
```yaml
sonarqube:
baseUrl: https://sonarqube-internal.example.com
externalUrl: https://sonarqube.example.com
apiKey: 123456789abcdef0123456789abcedf012
```
##### Multiple instance config
```yaml
sonarqube:
instances:
- name: default
baseUrl: https://default-sonarqube-internal.example.com
externalUrl: https://default-sonarqube.example.com
apiKey: 123456789abcdef0123456789abcedf012
- name: specialProject
baseUrl: https://special-project-sonarqube.example.com
apiKey: abcdef0123456789abcedf0123456789ab
```
## Links
- [Sonarqube Frontend](../sonarqube/README.md)
+14
View File
@@ -23,6 +23,13 @@ export interface Config {
*/
baseUrl?: string;
/**
* The external url of the sonarqube installation.
* Use this if you want to use a different url for the frontend than the backend.
* @visibility frontend
*/
externalUrl?: string;
/**
* The api key to access the sonarqube instance under baseUrl.
* @visibility secret
@@ -46,6 +53,13 @@ export interface Config {
*/
baseUrl: string;
/**
* The external url of the sonarqube instance.
* Use this if you want to use a different url for the frontend than the backend.
* @visibility frontend
*/
externalUrl?: string;
/**
* The api key to access the sonarqube instance.
* @visibility secret
@@ -24,7 +24,7 @@ import { SonarqubeFindings } from './sonarqubeInfoProvider';
describe('createRouter', () => {
let app: express.Express;
const getBaseUrlMock: jest.Mock<
{ baseUrl: string },
{ baseUrl: string; externalUrl?: string },
[{ instanceName: string }]
> = jest.fn();
const getFindingsMock: jest.Mock<
@@ -55,6 +55,7 @@ describe('createRouter', () => {
describe('GET /findings', () => {
const DUMMY_COMPONENT_KEY = 'my:component';
const DUMMY_INSTANCE_KEY = 'myInstance';
it('returns ok', async () => {
const measures = {
analysisDate: '2022-01-01T00:00:00Z',
@@ -77,6 +78,7 @@ describe('createRouter', () => {
expect(response.status).toEqual(200);
expect(response.body).toEqual(measures);
});
it('returns an error when component key is not defined', async () => {
const response = await request(app)
.get('/findings')
@@ -112,9 +114,12 @@ describe('createRouter', () => {
expect(response.body).toEqual(measures);
});
});
describe('GET /instanceUrl', () => {
const DUMMY_INSTANCE_KEY = 'myInstance';
const DUMMY_INSTANCE_URL = 'http://sonarqube.example.com';
const DUMMY_INSTANCE_URL = 'http://sonarqube-internal.example.com';
const DUMMY_INSTANCE_EXTERNAL_URL = 'http://sonarqube.example.com';
it('returns ok', async () => {
getBaseUrlMock.mockReturnValue({ baseUrl: DUMMY_INSTANCE_URL });
const response = await request(app)
@@ -141,5 +146,17 @@ describe('createRouter', () => {
expect(response.status).toEqual(200);
expect(response.body).toEqual({ instanceUrl: DUMMY_INSTANCE_URL });
});
it('returns the external url when provided', async () => {
getBaseUrlMock.mockReturnValue({
baseUrl: DUMMY_INSTANCE_URL,
externalUrl: DUMMY_INSTANCE_EXTERNAL_URL,
});
const response = await request(app).get('/instanceUrl').send();
expect(response.status).toEqual(200);
expect(response.body).toEqual({
instanceUrl: DUMMY_INSTANCE_EXTERNAL_URL,
});
});
});
});
@@ -81,11 +81,11 @@ export async function createRouter(
? `Retrieving sonarqube instance URL for key ${instanceKey}`
: `Retrieving default sonarqube instance URL as instanceKey is not provided`,
);
const { baseUrl } = sonarqubeInfoProvider.getBaseUrl({
const { baseUrl, externalUrl } = sonarqubeInfoProvider.getBaseUrl({
instanceName: instanceKey,
});
response.json({
instanceUrl: baseUrl,
instanceUrl: externalUrl || baseUrl,
});
});
@@ -27,6 +27,7 @@ describe('SonarqubeConfig', () => {
const SONARQUBE_DEFAULT_INSTANCE_NAME = 'default';
const DUMMY_SONAR_URL = 'https://sonarqube.example.com';
const DUMMY_SONAR_APIKEY = '123456789abcdef0123456789abcedf012';
const DUMMY_SIMPLE_OBJECT_FOR_DEFAULT_SONARQUBE_CONFIG = {
name: SONARQUBE_DEFAULT_INSTANCE_NAME,
baseUrl: DUMMY_SONAR_URL,
@@ -112,6 +113,7 @@ describe('SonarqubeConfig', () => {
},
]);
});
it('Throw an error if both a named default config and top level config', async () => {
expect(() =>
SonarqubeConfig.fromConfig(
@@ -299,6 +301,46 @@ describe('DefaultSonarqubeInfoProvider', () => {
baseUrl: 'https://sonarqube-other.example.com',
});
});
it('Provide external url for simple config', async () => {
const provider = configureProvider({
sonarqube: {
baseUrl: 'https://sonarqube-internal.example.com',
externalUrl: 'https://sonarqube.example.com',
apiKey: '123456789abcdef0123456789abcedf012',
},
});
expect(provider.getBaseUrl()).toEqual({
baseUrl: 'https://sonarqube-internal.example.com',
externalUrl: 'https://sonarqube.example.com',
});
});
it('Provide external url for named config', async () => {
const provider = configureProvider({
sonarqube: {
instances: [
{
name: 'default',
baseUrl: 'https://sonarqube.example.com',
apiKey: '123456789abcdef0123456789abcedf012',
},
{
name: 'other',
baseUrl: 'https://sonarqube-other-internal.example.com',
externalUrl: 'https://sonarqube-other.example.com',
apiKey: '123456789abcdef0123456789abcedf012',
},
],
},
});
expect(provider.getBaseUrl({ instanceName: 'other' })).toEqual({
baseUrl: 'https://sonarqube-other-internal.example.com',
externalUrl: 'https://sonarqube-other.example.com',
});
});
});
describe('getFindings', () => {
@@ -385,6 +427,7 @@ describe('DefaultSonarqubeInfoProvider', () => {
apiKey: DUMMY_API_KEY,
},
};
it('Provide findings when everything is ok', async () => {
setupHandlers();
const provider = configureProvider(DUMMY_SIMPLE_CONFIG_FOR_PROVIDER);
@@ -30,7 +30,10 @@ export interface SonarqubeInfoProvider {
* @param instanceName - Name of the sonarqube instance to get the info from
* @returns the url of the instance
*/
getBaseUrl(options?: { instanceName?: string }): { baseUrl: string };
getBaseUrl(options?: { instanceName?: string }): {
baseUrl: string;
externalUrl?: string;
};
/**
* Query the sonarqube instance corresponding to the instanceName to get all
@@ -96,6 +99,10 @@ export interface SonarqubeInstanceConfig {
* Base url to access the instance
*/
baseUrl: string;
/**
* External url to access the instance from the frontend
*/
externalUrl?: string;
/**
* Access token to access the sonarqube instance as generated in user profile.
*/
@@ -132,6 +139,7 @@ export class SonarqubeConfig {
sonarqubeConfig.getOptionalConfigArray('instances')?.map(c => ({
name: c.getString('name'),
baseUrl: c.getString('baseUrl'),
externalUrl: c.getOptionalString('externalUrl'),
apiKey: c.getString('apiKey'),
})) || [];
@@ -142,9 +150,10 @@ export class SonarqubeConfig {
// Get these as optional strings and check to give a better error message
const baseUrl = sonarqubeConfig.getOptionalString('baseUrl');
const externalUrl = sonarqubeConfig.getOptionalString('externalUrl');
const apiKey = sonarqubeConfig.getOptionalString('apiKey');
if (hasNamedDefault && (baseUrl || apiKey)) {
if (hasNamedDefault && (baseUrl || externalUrl || apiKey)) {
throw new Error(
`Found both a named sonarqube instance with name ${DEFAULT_SONARQUBE_NAME} and top level baseUrl or apiKey config. Use only one style of config.`,
);
@@ -160,10 +169,11 @@ export class SonarqubeConfig {
if (unnamedAllPresent) {
const unnamedInstanceConfig = [
{ name: DEFAULT_SONARQUBE_NAME, baseUrl, apiKey },
{ name: DEFAULT_SONARQUBE_NAME, baseUrl, externalUrl, apiKey },
] as {
name: string;
baseUrl: string;
externalUrl?: string;
apiKey: string;
}[];
@@ -303,11 +313,15 @@ export class DefaultSonarqubeInfoProvider implements SonarqubeInfoProvider {
*/
getBaseUrl(options: { instanceName?: string } = {}): {
baseUrl: string;
externalUrl?: string;
} {
const instanceConfig = this.config.getInstanceConfig({
sonarqubeName: options.instanceName,
});
return { baseUrl: instanceConfig.baseUrl };
return {
baseUrl: instanceConfig.baseUrl,
externalUrl: instanceConfig.externalUrl,
};
}
/**