Skip to content

Commit

Permalink
Handle fetching from inventory for users that don't have permissions …
Browse files Browse the repository at this point in the history
…to list all namespaces

Reference: kubev2v#1293

Handle providers fetching from inventory for users with limited namespace roles, who can only list their namespaces content
and don't have get permissions to all namespaces.

Before the fix, when those users tried to fetch providers inventory data, regarldess to
current namespace, the "inventory server is not reachable" error was always displayed.

After this fix, those users will not fetch all namepsaces but only
ones which they allow to list. So the fetching should now succeed and no
error should be displayed.

Signed-off-by: Sharon Gratch <[email protected]>
  • Loading branch information
sgratch committed Aug 22, 2024
1 parent 509aa55 commit 42c88ec
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export function StandardPage<T>({
[filteredData, currentPage, itemsPerPage],
);

const errorFetchingData = loaded && error;
const errorFetchingData = error;
const noResults = loaded && !error && sortedData.length == 0;
const noMatchingResults = loaded && !error && filteredData.length === 0 && sortedData.length > 0;

Expand Down Expand Up @@ -444,7 +444,7 @@ export function StandardPage<T>({
toId={toId}
expandedIds={expandedIds}
>
{!loaded && <Loading key="loading" title={t('Loading')} />}
{!loaded && !error && <Loading key="loading" title={t('Loading')} />}
{errorFetchingData && <ErrorState key="error" title={t('Unable to retrieve data')} />}
{noResults &&
(customNoResultsFound ?? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { useEffect, useRef, useState } from 'react';

import { ProviderInventory, ProvidersInventoryList } from '@kubev2v/types';
import { consoleFetchJSON } from '@openshift-console/dynamic-plugin-sdk';
import {
ProviderInventory,
ProviderModelGroupVersionKind,
ProvidersInventoryList,
V1beta1Provider,
} from '@kubev2v/types';
import {
consoleFetchJSON,
useFlag,
useK8sWatchResource,
} from '@openshift-console/dynamic-plugin-sdk';

import { getInventoryApiUrl, hasObjectChangedInGivenFields } from '../utils/helpers';

Expand All @@ -12,9 +21,11 @@ const INVENTORY_TYPES: string[] = ['openshift', 'openstack', 'ovirt', 'vsphere',
/**
* Configuration parameters for useProvidersInventoryList hook.
* @interface
* @property {string} namespace - namespace for fetching inventory's providers data for. Used only for users with limited namespaces privileges.
* @property {number} interval - Polling interval in milliseconds.
*/
interface UseInventoryParams {
namespace?: string;
interval?: number; // Polling interval in milliseconds
}

Expand All @@ -32,31 +43,55 @@ interface UseInventoryResult {
}

/**
* A React hook to fetch and maintain an up-to-date list of providers' inventory data.
* A React hook to fetch and maintain an up-to-date list of providers' inventory data, belongs to a given namespace or to all namespaces
* (based on the namespace parameter).
* For users with limited namespaces privileges, only the given namespace's providers inventory data are fetched.
* It fetches data on mount and then at the specified interval.
*
* @param {UseInventoryParams} params - Configuration parameters for the hook.
* @param {string} namespace - namespace to fetch providers' inventory data for. if set to null, then fetch for all namespaces.
* @param {number} [params.interval=10000] - Interval (in milliseconds) to fetch new data at.
*
* @returns {UseInventoryResult} result - Contains the inventory data, the loading state, and the error state.
*/
export const useProvidersInventoryList = ({
namespace = null,
interval = 20000,
}: UseInventoryParams): UseInventoryResult => {
const [inventory, setInventory] = useState<ProvidersInventoryList | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
const oldDataRef = useRef(null);
const oldErrorRef = useRef(null);
const canList: boolean = useFlag('CAN_LIST_NS');
const k8Providers: V1beta1Provider[] = !canList ? useK8sProvidersForNamespace(namespace) : null;

// Sanity check: if user does not have permissions to list all namespaces, exit now
if (!canList && namespace == null) {
const error = new Error("user don't have permissions to list all namespaces");
handleError(error);

return { inventory, loading: false, error };
}

useEffect(() => {
const fetchData = async () => {
try {
const newInventory: ProvidersInventoryList = await consoleFetchJSON(
getInventoryApiUrl(`providers?detail=1`),
);
// Fetch all providers
if (canList) {
const newInventory: ProvidersInventoryList = await consoleFetchJSON(
getInventoryApiUrl(`providers?detail=1`),
);
updateInventoryIfChanged(newInventory, DEFAULT_FIELDS_TO_COMPARE);
}
// Fetch single namespace's providers
else {
const newInventory: ProvidersInventoryList = await getAndUpdateInventoryProviders(
k8Providers,
);

updateInventoryIfChanged(newInventory, DEFAULT_FIELDS_TO_COMPARE);
updateInventoryIfChanged(newInventory, DEFAULT_FIELDS_TO_COMPARE);
}
handleError(null);
} catch (e) {
handleError(e);
Expand All @@ -67,7 +102,66 @@ export const useProvidersInventoryList = ({

const intervalId = setInterval(fetchData, interval);
return () => clearInterval(intervalId);
}, [interval]);
}, [interval, namespace, k8Providers]);

/**
* For users with limited namespaces privileges, Fetch only the given namespace's k8s providers
*
* @param {string} namespace namespace to fetch providers' inventory data for.
* @returns {V1beta1Provider[]} list of providers' k8s data, belongs to the given namespace, or null
* while loading and in case of a failure.
*/
function useK8sProvidersForNamespace(namespace: string): V1beta1Provider[] {
const [k8sProviders] = useK8sWatchResource<V1beta1Provider[]>({
groupVersionKind: ProviderModelGroupVersionKind,
namespaced: true,
isList: true,
namespace,
});

return k8sProviders;
}

/**
* For users with limited namespaces privileges, Fetch only the given namespace's inventory providers
* , handle errors if exist and update the UI if changed.
*
* @param {V1beta1Provider[]} k8Providers providers to fetch inventory data for.
* @returns {void}
*/
const getAndUpdateInventoryProviders = async (
k8Providers: V1beta1Provider[],
): Promise<ProvidersInventoryList> => {
const newInventory: ProvidersInventoryList = {
openshift: [],
openstack: [],
ovirt: [],
vsphere: [],
ova: [],
};

const allPromises = Promise.all(
k8Providers
?.filter((provider) => provider.status.phase === 'Ready')
.map(async (provider) => {
return await consoleFetchJSON(
getInventoryApiUrl(`providers/${provider.spec.type}/${provider.metadata.uid}`),
);
}),
)
.then((newInventoryProviders) => {
newInventoryProviders.map((newInventoryProvider) =>
newInventory[newInventoryProvider.type].push(newInventoryProvider),
);

return newInventory;
})
.catch((error) => {
throw error;
});

return allPromises;
};

/**
* Handles any errors thrown when trying to fetch the inventory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ const ProvidersListPage: React.FC<{
inventory,
loading: inventoryLoading,
error: inventoryError,
} = useProvidersInventoryList({});
} = useProvidersInventoryList({ namespace });

const permissions = useGetDeleteAndEditAccessReview({
model: ProviderModel,
Expand Down

0 comments on commit 42c88ec

Please sign in to comment.