Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions libs/ui-lib-tests/cypress/support/interceptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ const addClusterPatchAndDetailsIntercepts = () => {

const addDay1InfraEnvIntercepts = () => {
const infraEnvApiPath = getDay1InfraEnvApiPath();
let infraEnvCreated = false;

// Actions on particular infraEnv
cy.intercept('GET', infraEnvApiPath, mockInfraEnvResponse).as('infra-env-details');

Expand All @@ -258,11 +260,26 @@ const addDay1InfraEnvIntercepts = () => {
// Actions on all the infraEnvs
cy.intercept('PATCH', infraEnvApiPath, mockInfraEnvResponse).as('update-infra-env');

cy.intercept('GET', `${allInfraEnvsApiPath}?cluster_id=${Cypress.env('clusterId')}`, [
fixtures.baseInfraEnv,
]).as('filter-infra-envs');
cy.intercept('GET', `${allInfraEnvsApiPath}?cluster_id=${Cypress.env('clusterId')}`, (req) => {
// Return empty array until POST creates the infraEnv, or if signal indicates it already exists
// Signal starts as '' and is set to a value after cluster/infraenv creation
const currentSignal = Cypress.env('AI_LAST_SIGNAL');
const willReturnFixture = infraEnvCreated || (currentSignal !== '' && currentSignal !== undefined && currentSignal !== null);

console.log(`GET infra-envs: signal="${currentSignal}", infraEnvCreated=${infraEnvCreated}, returning ${willReturnFixture ? 'fixture' : 'empty'}`);

if (willReturnFixture) {
req.reply([fixtures.baseInfraEnv]);
} else {
req.reply([]);
}
}).as('filter-infra-envs');

cy.intercept('POST', allInfraEnvsApiPath, mockInfraEnvResponse).as('create-infra-env');
cy.intercept('POST', allInfraEnvsApiPath, (req) => {
console.log('POST infra-env');
infraEnvCreated = true;
mockInfraEnvResponse(req);
}).as('create-infra-env');
};

const getDay2InfraEnvByCpuArch = (req) => {
Expand Down
31 changes: 18 additions & 13 deletions libs/ui-lib/lib/ocm/hooks/useInfraEnvId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,29 @@ export default function useInfraEnvId(

const findInfraEnvId = React.useCallback(async () => {
try {
const infraEnvId = await InfraEnvsService.getInfraEnvId(
clusterId,
cpuArchitecture,
isSingleClusterFeatureEnabled,
);
if (infraEnvId && !(infraEnvId instanceof Error)) {
setInfraEnv(infraEnvId);
} else {
//If infraEnv doesn't exist create a new one
if (pullSecret) {
const infraEnv = await InfraEnvsService.create({
if (pullSecret) {
const infraEnv = await InfraEnvsService.getOrCreate(
{
name: InfraEnvsService.makeInfraEnvName(cpuArchitecture, clusterName),
pullSecret,
clusterId,
openshiftVersion,
cpuArchitecture: cpuArchitecture as InfraEnvCreateParams['cpuArchitecture'],
});
setInfraEnv(infraEnv.id);
},
isSingleClusterFeatureEnabled,
);
setInfraEnv(infraEnv.id);
Comment on lines +25 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify the type assertion for cpuArchitecture.

The logic correctly uses getOrCreate when pullSecret is available, which aligns with the PR objectives. However, the type assertion on line 32 should be verified to ensure type safety.

Run the following script to verify that the CpuArchitecture type matches the expected union type:


🏁 Script executed:

#!/bin/bash
# Description: Verify CpuArchitecture type definition matches InfraEnvCreateParams['cpuArchitecture']

# Search for CpuArchitecture type definition
echo "=== Searching for CpuArchitecture type definition ==="
ast-grep --pattern 'type CpuArchitecture = $$$'

echo ""
echo "=== Searching for CpuArchitecture export ==="
rg -nP --type=ts "export (type|interface) CpuArchitecture" -A 3

echo ""
echo "=== Expected type from InfraEnvCreateParams ==="
echo "cpuArchitecture?: 'x86_64' | 'aarch64' | 'arm64' | 'ppc64le' | 's390x'"

Length of output: 549


🏁 Script executed:

#!/bin/bash
# Description: Search for CpuArchitecture type and InfraEnvCreateParams definitions

echo "=== Full content of cpuArchitecture.ts ==="
cat libs/ui-lib/lib/common/types/cpuArchitecture.ts

echo ""
echo "=== Search for InfraEnvCreateParams definition ==="
rg -nP --type=ts "InfraEnvCreateParams" -A 5 -B 1 | head -50

echo ""
echo "=== Search for cpuArchitecture import/usage in useInfraEnvId.ts ==="
rg -nP --type=ts "cpuArchitecture" libs/ui-lib/lib/ocm/hooks/useInfraEnvId.ts -B 2 -A 2

Length of output: 8339


🏁 Script executed:

#!/bin/bash
# Description: Find where useInfraEnvId is called and what values are passed

echo "=== Search for useInfraEnvId call sites ==="
rg -nP --type=ts "useInfraEnvId\(" -B 2 -A 3 | head -100

echo ""
echo "=== Check if SupportedCpuArchitecture is used elsewhere for similar pattern ==="
rg -nP --type=ts "SupportedCpuArchitecture" libs/ui-lib/lib/ocm/ -B 1 -A 1 | head -50

echo ""
echo "=== Check InfraEnvsService.getOrCreate implementation ==="
rg -nP --type=ts "async getOrCreate" libs/ui-lib/lib/ocm/services/InfraEnvsService.ts -A 20 | head -40

Length of output: 5948


Narrow the cpuArchitecture parameter type or add runtime validation before the type assertion.

The type assertion on line 32 is unsafe. The function parameter accepts the full CpuArchitecture enum (which includes 'multi' and 'cluster-day1-cpu-architecture'), but casts it to InfraEnvCreateParams['cpuArchitecture'] (which only accepts 'x86_64' | 'aarch64' | 'arm64' | 'ppc64le' | 's390x'). If invalid architecture values are passed, they will bypass the cast and cause runtime errors at the API layer.

Either:

  • Change the parameter type to SupportedCpuArchitecture or create a narrower type that matches InfraEnvCreateParams['cpuArchitecture'], or
  • Add validation in getOrCreate (lines 79–81 of InfraEnvsService.ts) to reject invalid values before use.

Note: The same pattern exists in ClustersService.ts line 43 and should also be addressed.

🤖 Prompt for AI Agents
In libs/ui-lib/lib/ocm/hooks/useInfraEnvId.ts around lines 25 to 36, the
cpuArchitecture is being unsafely asserted to
InfraEnvCreateParams['cpuArchitecture']; narrow the accepted type or validate at
runtime to prevent invalid values (like 'multi' or
'cluster-day1-cpu-architecture') reaching the API. Fix by changing the hook
parameter type to a SupportedCpuArchitecture (matching
InfraEnvCreateParams['cpuArchitecture']) or add a runtime guard that maps/throws
for unsupported enum values before calling InfraEnvsService.getOrCreate; also
apply the same type narrowing or validation in ClustersService.ts at line 43 to
keep behavior consistent.

} else {
// No pullSecret available, just try to find existing InfraEnv
const infraEnvId = await InfraEnvsService.getInfraEnvId(
clusterId,
cpuArchitecture,
isSingleClusterFeatureEnabled,
);
if (infraEnvId && !(infraEnvId instanceof Error)) {
setInfraEnv(infraEnvId);
} else if (infraEnvId instanceof Error) {
setError(getErrorMessage(infraEnvId));
}
}
} catch (e) {
Expand Down
4 changes: 1 addition & 3 deletions libs/ui-lib/lib/ocm/services/ClustersService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ const ClustersService = {
infraEnvCreateParams.imageType = 'minimal-iso';
}

if (!isSingleClusterFeatureEnabled) {
await InfraEnvsService.create(infraEnvCreateParams);
}
await InfraEnvsService.getOrCreate(infraEnvCreateParams, isSingleClusterFeatureEnabled);

return cluster;
},
Expand Down
42 changes: 42 additions & 0 deletions libs/ui-lib/lib/ocm/services/InfraEnvsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CpuArchitecture } from '../../common';
import { InfraEnvsAPI } from './apis';
import InfraEnvCache from './InfraEnvIdsCacheService';
import { getDummyInfraEnvField } from '../components/clusterConfiguration/staticIp/data/dummyData';
import { isAxiosError } from '../../common/api/axiosExtensions';
import {
Cluster,
HostStaticNetworkConfig,
Expand Down Expand Up @@ -67,6 +68,47 @@ const InfraEnvsService = {
return infraEnv;
},

async getOrCreate(
params: InfraEnvCreateParams,
isSingleClusterFeatureEnabled?: boolean,
): Promise<InfraEnv> {
if (!params.clusterId) {
throw new Error('Cannot create InfraEnv, clusterId is missing');
}

if (!params.cpuArchitecture) {
throw new Error('Cannot get or create InfraEnv, cpuArchitecture is missing');
}

// Check if an InfraEnv already exists
try {
const existingInfraEnvId = await InfraEnvsService.getInfraEnvId(
params.clusterId,
params.cpuArchitecture as CpuArchitecture,
isSingleClusterFeatureEnabled,
);

if (existingInfraEnvId && !(existingInfraEnvId instanceof Error)) {
// InfraEnv exists, fetch and return it
const { data: infraEnv } = await InfraEnvsAPI.get(existingInfraEnvId);
// Update cache to maintain consistency
InfraEnvCache.updateInfraEnvs(params.clusterId, [infraEnv]);
return infraEnv;
}
} catch (error) {
// Only suppress 404 (not found) errors - let other errors propagate
if (isAxiosError(error) && error.response?.status === 404) {
// Fall through to create a new InfraEnv
} else {
// Re-throw authentication, network, and other unexpected errors
throw error;
}
}

// No InfraEnv exists, create one
return InfraEnvsService.create(params);
},

async removeAll(clusterId: Cluster['id']) {
const { data: infraEnvs } = await InfraEnvsAPI.list(clusterId);

Expand Down
Loading