This commit is contained in:
Joey Orlando 2023-06-29 13:47:39 +02:00 committed by GitHub
commit 8c768f2a70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 40 deletions

View file

@ -7,22 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## v1.3.3
## v1.3.4 (2023-06-29)
This version contains just some small cleanup and CI changes 🙂
## v1.3.3 (2023-06-28)
## v1.3.2 (2023-06-29)
### Added
- Add metric "how many alert groups user was notified of" to Prometheus exporter ([#2334](https://github.com/grafana/oncall/pull/2334/))
## v1.3.2 (2023-06-28)
### Changed
- Change permissions used during setup to better represent actions being taken by @mderynck ([#2242](https://github.com/grafana/oncall/pull/2242))
- Display 100000+ in stats when there are more than 100000 alert groups in the result ([#1901](https://github.com/grafana/oncall/pull/1901))
- Change OnCall plugin to use service accounts and api tokens for communicating with backend, by @mderynck ([#2385](https://github.com/grafana/oncall/pull/2385))
### Fixed

View file

@ -136,22 +136,75 @@ class PluginState {
this.grafanaBackend.post(this.GRAFANA_PLUGIN_SETTINGS_URL, { ...data, enabled, pinned: true });
static readonly KEYS_BASE_URL = '/api/auth/keys';
static readonly ONCALL_KEY_NAME = 'OnCall';
static readonly SERVICE_ACCOUNTS_BASE_URL = '/api/serviceaccounts';
static readonly ONCALL_SERVICE_ACCOUNT_NAME = 'sa-autogen-OnCall';
static readonly SERVICE_ACCOUNTS_SEARCH_URL = `${PluginState.SERVICE_ACCOUNTS_BASE_URL}/search?query=${PluginState.ONCALL_SERVICE_ACCOUNT_NAME}`;
static getGrafanaToken = async () => {
const keys = await this.grafanaBackend.get(this.KEYS_BASE_URL);
return keys.find((key: { id: number; name: string; role: string }) => key.name === 'OnCall');
static getServiceAccount = async () => {
const serviceAccounts = await this.grafanaBackend.get(this.SERVICE_ACCOUNTS_SEARCH_URL);
return serviceAccounts.serviceAccounts.length > 0 ? serviceAccounts.serviceAccounts[0] : null;
};
static getOrCreateServiceAccount = async () => {
const serviceAccount = await this.getServiceAccount();
if (serviceAccount) {
return serviceAccount;
}
return await this.grafanaBackend.post(this.SERVICE_ACCOUNTS_BASE_URL, {
name: this.ONCALL_SERVICE_ACCOUNT_NAME,
role: 'Admin',
isDisabled: false,
});
};
static getTokenFromServiceAccount = async (serviceAccount) => {
const tokens = await this.grafanaBackend.get(`${this.SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`);
return tokens.find((key: { id: number; name: string; role: string }) => key.name === PluginState.ONCALL_KEY_NAME);
};
/**
* This will satisfy a check for an existing key regardless of if the key is an older api key or under a
* service account.
*/
static getGrafanaToken = async () => {
const serviceAccount = await this.getServiceAccount();
if (serviceAccount) {
return await this.getTokenFromServiceAccount(serviceAccount);
}
const keys = await this.grafanaBackend.get(this.KEYS_BASE_URL);
const oncallApiKeys = keys.find(
(key: { id: number; name: string; role: string }) => key.name === PluginState.ONCALL_KEY_NAME
);
if (oncallApiKeys) {
return oncallApiKeys;
}
return null;
};
/**
* Create service account and api token belonging to it instead of using api keys
*/
static createGrafanaToken = async () => {
const serviceAccount = await this.getOrCreateServiceAccount();
const existingToken = await this.getTokenFromServiceAccount(serviceAccount);
if (existingToken) {
await this.grafanaBackend.delete(
`${this.SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens/${existingToken.id}`
);
}
const existingKey = await this.getGrafanaToken();
if (existingKey) {
await this.grafanaBackend.delete(`${this.KEYS_BASE_URL}/${existingKey.id}`);
}
return await this.grafanaBackend.post(this.KEYS_BASE_URL, {
name: 'OnCall',
return await this.grafanaBackend.post(`${this.SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`, {
name: PluginState.ONCALL_KEY_NAME,
role: 'Admin',
secondsToLive: null,
});
};

View file

@ -179,38 +179,59 @@ describe('PluginState.updateGrafanaPluginSettings', () => {
});
describe('PluginState.createGrafanaToken', () => {
test.each([true, false])('it calls the proper methods - existing key: %s', async (onCallKeyExists) => {
const baseUrl = '/api/auth/keys';
const onCallKeyId = 12345;
const onCallKeyName = 'OnCall';
const onCallKey = { name: onCallKeyName, id: onCallKeyId };
const existingKeys = [{ name: 'foo', id: 9595 }];
const cases = [
[true, true, false],
[true, false, false],
[false, true, true],
[false, true, false],
[false, false, false],
];
PluginState.grafanaBackend.get = jest
.fn()
.mockResolvedValueOnce(onCallKeyExists ? [...existingKeys, onCallKey] : existingKeys);
PluginState.grafanaBackend.delete = jest.fn();
PluginState.grafanaBackend.post = jest.fn();
test.each(cases)(
'it calls the proper methods - existing key: %s, existing sa: %s, existing token: %s',
async (apiKeyExists, saExists, apiTokenExists) => {
const baseUrl = PluginState.KEYS_BASE_URL;
const serviceAccountBaseUrl = PluginState.SERVICE_ACCOUNTS_BASE_URL;
const apiKeyId = 12345;
const apiKeyName = PluginState.ONCALL_KEY_NAME;
const apiKey = { name: apiKeyName, id: apiKeyId };
const saId = 33333;
const serviceAccount = { id: saId };
await PluginState.createGrafanaToken();
PluginState.getGrafanaToken = jest.fn().mockReturnValueOnce(apiKeyExists ? apiKey : null);
PluginState.grafanaBackend.delete = jest.fn();
PluginState.grafanaBackend.post = jest.fn();
expect(PluginState.grafanaBackend.get).toHaveBeenCalledTimes(1);
expect(PluginState.grafanaBackend.get).toHaveBeenCalledWith(baseUrl);
PluginState.getServiceAccount = jest.fn().mockReturnValueOnce(saExists ? serviceAccount : null);
PluginState.getOrCreateServiceAccount = jest.fn().mockReturnValueOnce(serviceAccount);
PluginState.getTokenFromServiceAccount = jest.fn().mockReturnValueOnce(apiTokenExists ? apiKey : null);
if (onCallKeyExists) {
expect(PluginState.grafanaBackend.delete).toHaveBeenCalledTimes(1);
expect(PluginState.grafanaBackend.delete).toHaveBeenCalledWith(`${baseUrl}/${onCallKeyId}`);
} else {
expect(PluginState.grafanaBackend.delete).not.toHaveBeenCalled();
await PluginState.createGrafanaToken();
expect(PluginState.getGrafanaToken).toHaveBeenCalledTimes(1);
if (apiKeyExists) {
expect(PluginState.grafanaBackend.delete).toHaveBeenCalledTimes(1);
expect(PluginState.grafanaBackend.delete).toHaveBeenCalledWith(`${baseUrl}/${apiKey.id}`);
} else if (apiTokenExists) {
expect(PluginState.grafanaBackend.delete).toHaveBeenCalledTimes(1);
expect(PluginState.grafanaBackend.delete).toHaveBeenCalledWith(
`${serviceAccountBaseUrl}/${serviceAccount.id}/tokens/${apiKey.id}`
);
} else {
expect(PluginState.grafanaBackend.delete).not.toHaveBeenCalled();
}
expect(PluginState.grafanaBackend.post).toHaveBeenCalledTimes(1);
expect(PluginState.grafanaBackend.post).toHaveBeenCalledWith(
`${serviceAccountBaseUrl}/${serviceAccount.id}/tokens`,
{
name: apiKeyName,
role: 'Admin',
}
);
}
expect(PluginState.grafanaBackend.post).toHaveBeenCalledTimes(1);
expect(PluginState.grafanaBackend.post).toHaveBeenCalledWith(baseUrl, {
name: onCallKeyName,
role: 'Admin',
secondsToLive: null,
});
});
);
});
describe('PluginState.getPluginSyncStatus', () => {