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: #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 14, 2024
1 parent 509aa55 commit 1376b95
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react';

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

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

Expand All @@ -12,9 +12,11 @@ const INVENTORY_TYPES: string[] = ['openshift', 'openstack', 'ovirt', 'vsphere',
/**
* Configuration parameters for useProvidersInventoryList hook.
* @interface
* @property {V1beta1Provider[]} currNamespaceProviders - list of namespace's providers to fetch inventory data for.
* @property {number} interval - Polling interval in milliseconds.
*/
interface UseInventoryParams {
currNamespaceProviders?: V1beta1Provider[];
interval?: number; // Polling interval in milliseconds
}

Expand All @@ -36,27 +38,42 @@ interface UseInventoryResult {
* It fetches data on mount and then at the specified interval.
*
* @param {UseInventoryParams} params - Configuration parameters for the hook.
* @param {V1beta1Provider[]} currNamespaceProviders - list of namespace's providers to fetch inventory data for.
* @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 = ({
currNamespaceProviders = 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');

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);
updateInventoryIfChanged(newInventory, DEFAULT_FIELDS_TO_COMPARE);
}
// Fetch current namespace's providers
else {
currNamespaceProviders?.forEach(async (provider) => {
const newProviderInventory = await consoleFetchJSON(
getInventoryApiUrl(`providers/${provider.spec.type}/${provider.metadata.uid}`),
);
updateInventoryProviderIfChanged(newProviderInventory, DEFAULT_FIELDS_TO_COMPARE);
});
}
handleError(null);
} catch (e) {
handleError(e);
Expand All @@ -67,7 +84,7 @@ export const useProvidersInventoryList = ({

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

/**
* Handles any errors thrown when trying to fetch the inventory.
Expand Down Expand Up @@ -148,6 +165,58 @@ export const useProvidersInventoryList = ({
}
}

function updateInventoryProviderIfChanged(
newProviderInventory: ProviderInventory,
fieldsToCompare: string[],
): void {
if (
!newProviderInventory ||
newProviderInventory.uid == null ||
newProviderInventory.type == null
) {
setLoading(false);
return;
}

let needReRender = false;
// Initialize the new inventory list based on an empty list or the current one
const newInventoryList: ProvidersInventoryList =
oldDataRef.current?.inventoryList == null
? { openshift: [], openstack: [], ovirt: [], vsphere: [], ova: [] }
: oldDataRef.current?.inventoryList;

// Create a map of old inventory, using 'uid' as the key and search for current inventory updated item.
// If a matching item is not found in the new list, or the item has changed, we need to update the inventory list and re-render.
const oldFlatInventory = INVENTORY_TYPES.flatMap<ProviderInventory>(
(type) => oldDataRef.current?.inventoryList?.[type] || [],
);
const oldInventoryMap = new Map(oldFlatInventory.map((item) => [item.uid, item]));
const oldProviderInventory = oldInventoryMap.get(newProviderInventory.uid);

if (!oldProviderInventory) {
newInventoryList[newProviderInventory.type].push(newProviderInventory);
needReRender = true;
} else if (
hasObjectChangedInGivenFields({
oldObject: oldProviderInventory,
newObject: newProviderInventory,
fieldsToCompare,
})
) {
newInventoryList[newProviderInventory.type].forEach((item, i) => {
if (item === oldProviderInventory)
newInventoryList[newProviderInventory.type][i] = newProviderInventory;
});
needReRender = true;
}

if (needReRender) {
setInventory(newInventoryList);
setLoading(false);
oldDataRef.current = { inventoryList: newInventoryList };
}
}

return { inventory, loading, error };
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,16 @@ const ProvidersListPage: React.FC<{
namespace,
});

// A list of providers belong to the active namespace
const currNamespaceProviders: V1beta1Provider[] = providers.filter(
(provider) => provider.metadata?.namespace === namespace,
);

const {
inventory,
loading: inventoryLoading,
error: inventoryError,
} = useProvidersInventoryList({});
} = useProvidersInventoryList({ currNamespaceProviders });

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

0 comments on commit 1376b95

Please sign in to comment.