mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
ServiceAccounts: API keys migration (#50002)
* ServiceAccounts: able to get upgrade status * Banner with API keys migration info * Show API keys migration info on Service accounts page * Migrate individual API keys * Use transaction for key migration * Migrate all api keys to service accounts * Hide api keys after migration * Migrate API keys separately for each org * Revert API key * Revert key API method * Rename migration actions and reducers * Fix linter errors * Tests for migrating single API key * Tests for migrating all api keys * More tests * Fix reverting tokens * API: rename convert to migrate * Add api route descriptions to methods * rearrange methods in api.go * Refactor: rename and move some methods * Prevent assigning tokens to non-existing service accounts * Refactor: ID TO Id * Refactor: fix error message * Delete service account if migration failed * Fix linter errors
This commit is contained in:
@@ -22,6 +22,7 @@ const setup = (propOverrides: Partial<Props>) => {
|
||||
const updateServiceAccountMock = jest.fn();
|
||||
const changeStateFilterMock = jest.fn();
|
||||
const createServiceAccountTokenMock = jest.fn();
|
||||
const getApiKeysMigrationStatusMock = jest.fn();
|
||||
const props: Props = {
|
||||
navModel: {
|
||||
main: {
|
||||
@@ -41,6 +42,7 @@ const setup = (propOverrides: Partial<Props>) => {
|
||||
showPaging: false,
|
||||
totalPages: 1,
|
||||
serviceAccounts: [],
|
||||
apiKeysMigrated: false,
|
||||
changeQuery: changeQueryMock,
|
||||
fetchACOptions: fetchACOptionsMock,
|
||||
fetchServiceAccounts: fetchServiceAccountsMock,
|
||||
@@ -48,6 +50,7 @@ const setup = (propOverrides: Partial<Props>) => {
|
||||
updateServiceAccount: updateServiceAccountMock,
|
||||
changeStateFilter: changeStateFilterMock,
|
||||
createServiceAccountToken: createServiceAccountTokenMock,
|
||||
getApiKeysMigrationStatus: getApiKeysMigrationStatusMock,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { GrafanaTheme2, OrgRole } from '@grafana/data';
|
||||
import { ConfirmModal, FilterInput, Icon, LinkButton, RadioButtonGroup, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { Alert, ConfirmModal, FilterInput, Icon, LinkButton, RadioButtonGroup, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
updateServiceAccount,
|
||||
changeStateFilter,
|
||||
createServiceAccountToken,
|
||||
getApiKeysMigrationStatus,
|
||||
} from './state/actions';
|
||||
|
||||
interface OwnProps {}
|
||||
@@ -43,6 +44,7 @@ const mapDispatchToProps = {
|
||||
updateServiceAccount,
|
||||
changeStateFilter,
|
||||
createServiceAccountToken,
|
||||
getApiKeysMigrationStatus,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
@@ -55,6 +57,7 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
builtInRoles,
|
||||
query,
|
||||
serviceAccountStateFilter,
|
||||
apiKeysMigrated,
|
||||
changeQuery,
|
||||
fetchACOptions,
|
||||
fetchServiceAccounts,
|
||||
@@ -62,6 +65,7 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
updateServiceAccount,
|
||||
changeStateFilter,
|
||||
createServiceAccountToken,
|
||||
getApiKeysMigrationStatus,
|
||||
}: Props): JSX.Element => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
@@ -72,10 +76,11 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
|
||||
useEffect(() => {
|
||||
fetchServiceAccounts({ withLoadingIndicator: true });
|
||||
getApiKeysMigrationStatus();
|
||||
if (contextSrv.licensedAccessControlEnabled()) {
|
||||
fetchACOptions();
|
||||
}
|
||||
}, [fetchACOptions, fetchServiceAccounts]);
|
||||
}, [fetchACOptions, fetchServiceAccounts, getApiKeysMigrationStatus]);
|
||||
|
||||
const noServiceAccountsCreated =
|
||||
serviceAccounts.length === 0 && serviceAccountStateFilter === ServiceAccountStateFilter.All && !query;
|
||||
@@ -151,9 +156,21 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
setCurrentServiceAccount(null);
|
||||
};
|
||||
|
||||
const onMigrationInfoClose = () => {
|
||||
// TODO: dismiss banner permanently
|
||||
};
|
||||
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
{apiKeysMigrated && (
|
||||
<Alert
|
||||
title="API keys migrated to Service accounts. Your keys are now called tokens and live inside respective service
|
||||
accounts. Learn more."
|
||||
severity="success"
|
||||
onRemove={onMigrationInfoClose}
|
||||
></Alert>
|
||||
)}
|
||||
<div className={styles.pageHeader}>
|
||||
<h2>Service accounts</h2>
|
||||
<div className={styles.apiKeyInfoLabel}>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
serviceAccountsFetchBegin,
|
||||
serviceAccountsFetched,
|
||||
serviceAccountsFetchEnd,
|
||||
apiKeysMigrationStatusLoaded,
|
||||
stateFilterChanged,
|
||||
} from './reducers';
|
||||
|
||||
@@ -41,6 +42,13 @@ export function fetchACOptions(): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function getApiKeysMigrationStatus(): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const result = await getBackendSrv().get('/api/serviceaccounts/migrationstatus');
|
||||
dispatch(apiKeysMigrationStatusLoaded(!!result?.migrated));
|
||||
};
|
||||
}
|
||||
|
||||
interface FetchServiceAccountsParams {
|
||||
withLoadingIndicator: boolean;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export const initialStateList: ServiceAccountsState = {
|
||||
totalPages: 1,
|
||||
showPaging: false,
|
||||
serviceAccountStateFilter: ServiceAccountStateFilter.All,
|
||||
apiKeysMigrated: false,
|
||||
};
|
||||
|
||||
interface ServiceAccountsFetched {
|
||||
@@ -89,6 +90,9 @@ const serviceAccountsSlice = createSlice({
|
||||
builtInRolesLoaded: (state, action: PayloadAction<Record<string, Role[]>>): ServiceAccountsState => {
|
||||
return { ...state, builtInRoles: action.payload };
|
||||
},
|
||||
apiKeysMigrationStatusLoaded: (state, action): ServiceAccountsState => {
|
||||
return { ...state, apiKeysMigrated: action.payload };
|
||||
},
|
||||
queryChanged: (state, action: PayloadAction<string>) => {
|
||||
return {
|
||||
...state,
|
||||
@@ -114,6 +118,7 @@ export const {
|
||||
serviceAccountsFetched,
|
||||
acOptionsLoaded,
|
||||
builtInRolesLoaded,
|
||||
apiKeysMigrationStatusLoaded,
|
||||
pageChanged,
|
||||
stateFilterChanged,
|
||||
queryChanged,
|
||||
|
||||
Reference in New Issue
Block a user