From 336a567c90c3d3aea8faa45123d05ae1beadd2f6 Mon Sep 17 00:00:00 2001 From: Dmitry Gusev Date: Tue, 15 Oct 2024 13:55:50 +0300 Subject: [PATCH] Add CAPV support (#4630) --- scripts/generate/mapi-resources.yaml | 20 + .../ClusterDetailWidgetControlPlaneNodes.tsx | 35 +- .../ClusterDetailWidgetProvider.tsx | 6 + .../ClusterDetailWidgetProviderVSphere.tsx | 48 + .../ClusterDetailWidgetControlPlaneNodes.tsx | 2 +- .../CreateClusterAppBundles/schemaUtils.ts | 33 +- .../permissions/usePermissionsForCPNodes.ts | 40 + .../permissions/usePermissionsForClusters.ts | 39 + .../usePermissionsForControlPlanes.ts | 1 + src/components/MAPI/clusters/utils.ts | 77 +- .../OrganizationDetailGeneral/utils.ts | 99 +- src/components/MAPI/releases/utils.ts | 2 +- src/components/MAPI/types.ts | 8 +- src/components/MAPI/utils.ts | 101 +- .../workernodes/ClusterDetailWorkerNodes.tsx | 218 ++- .../workernodes/WorkerNodesNodePoolItem.tsx | 92 +- .../__tests__/WorkerNodesNodePoolItem.tsx | 55 +- .../permissions/usePermissionsForNodePools.ts | 40 + .../UI/Display/MAPI/workernodes/styles.tsx | 12 +- .../mapi/capvv1beta1/getVSphereCluster.ts | 31 + .../mapi/capvv1beta1/getVSphereMachine.ts | 31 + .../mapi/capvv1beta1/getVSphereMachineList.ts | 43 + .../capvv1beta1/getVSphereMachineTemplate.ts | 31 + .../getVSphereMachineTemplateList.ts | 47 + src/model/services/mapi/capvv1beta1/index.ts | 6 + src/model/services/mapi/capvv1beta1/types.ts | 1330 +++++++++++++++++ src/utils/helpers.ts | 9 + 27 files changed, 2304 insertions(+), 152 deletions(-) create mode 100644 src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProviderVSphere.tsx create mode 100644 src/model/services/mapi/capvv1beta1/getVSphereCluster.ts create mode 100644 src/model/services/mapi/capvv1beta1/getVSphereMachine.ts create mode 100644 src/model/services/mapi/capvv1beta1/getVSphereMachineList.ts create mode 100644 src/model/services/mapi/capvv1beta1/getVSphereMachineTemplate.ts create mode 100644 src/model/services/mapi/capvv1beta1/getVSphereMachineTemplateList.ts create mode 100644 src/model/services/mapi/capvv1beta1/index.ts create mode 100644 src/model/services/mapi/capvv1beta1/types.ts diff --git a/scripts/generate/mapi-resources.yaml b/scripts/generate/mapi-resources.yaml index e7a41eac8d..809ad609e6 100644 --- a/scripts/generate/mapi-resources.yaml +++ b/scripts/generate/mapi-resources.yaml @@ -82,3 +82,23 @@ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 crdURL: https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-azure/main/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml +- apiVersionAlias: capvv1beta1 + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + resources: + - name: VSphereCluster + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + crdURL: https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-vsphere/refs/heads/main/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml + verbs: + - get + - name: VSphereMachine + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + crdURL: https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-vsphere/refs/heads/main/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachines.yaml + verbs: + - get + - list + - name: VSphereMachineTemplate + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + crdURL: https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-vsphere/refs/heads/main/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachinetemplates.yaml + verbs: + - get + - list diff --git a/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetControlPlaneNodes.tsx b/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetControlPlaneNodes.tsx index 74516fa0bf..3bcfd8ea63 100644 --- a/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetControlPlaneNodes.tsx +++ b/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetControlPlaneNodes.tsx @@ -11,6 +11,7 @@ import { fetchControlPlaneForClusterKey, fetchControlPlaneNodesForCluster, fetchControlPlaneNodesForClusterKey, + supportsAvailabilityZones, } from 'MAPI/utils'; import { GenericResponseError } from 'model/clients/GenericResponseError'; import { Providers } from 'model/constants'; @@ -166,6 +167,9 @@ const ClusterDetailWidgetControlPlaneNodes: React.FC< controlPlaneNodes ); + const displayAvailabilityZones = + cluster && supportsAvailabilityZones(cluster); + const [isSwitchingToHA, setIsSwitchingToHA] = useState(false); const onSwitchToHAExit = () => { @@ -205,19 +209,24 @@ const ClusterDetailWidgetControlPlaneNodes: React.FC< ) } - - - {formatAvailabilityZonesLabel(provider, stats.availabilityZones)} - - - {(value) => ( - - )} - + + {displayAvailabilityZones && ( + <> + + + {formatAvailabilityZonesLabel(provider, stats.availabilityZones)} + + + {(value) => ( + + )} + + + )} {canSwitchToHA && ( diff --git a/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProvider.tsx b/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProvider.tsx index 74a17bf117..8a090db300 100644 --- a/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProvider.tsx +++ b/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProvider.tsx @@ -10,6 +10,7 @@ import { extractErrorMessage } from 'MAPI/utils'; import { GenericResponseError } from 'model/clients/GenericResponseError'; import * as capav1beta2 from 'model/services/mapi/capav1beta2'; import * as capgv1beta1 from 'model/services/mapi/capgv1beta1'; +import * as capvv1beta1 from 'model/services/mapi/capvv1beta1'; import * as capzv1beta1 from 'model/services/mapi/capzv1beta1'; import * as legacyCredentials from 'model/services/mapi/legacy/credentials'; import { selectOrganizations } from 'model/stores/organization/selectors'; @@ -28,6 +29,7 @@ import ClusterDetailWidgetProviderAWSManaged from './ClusterDetailWidgetProvider import ClusterDetailWidgetProviderAzure from './ClusterDetailWidgetProviderAzure'; import ClusterDetailWidgetProviderCAPG from './ClusterDetailWidgetProviderCAPG'; import ClusterDetailWidgetProviderLoader from './ClusterDetailWidgetProviderLoader'; +import ClusterDetailWidgetProviderVSphere from './ClusterDetailWidgetProviderVSphere'; import { fetchProviderCredential, fetchProviderCredentialKey } from './utils'; interface IClusterDetailWidgetProviderProps @@ -145,6 +147,10 @@ const ClusterDetailWidgetProvider: React.FC< | legacyCredentials.ICredential } /> + ) : kind === capvv1beta1.VSphereCluster ? ( + ) : null} diff --git a/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProviderVSphere.tsx b/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProviderVSphere.tsx new file mode 100644 index 0000000000..8267ed5ecf --- /dev/null +++ b/src/components/MAPI/clusters/ClusterDetail/ClusterDetailWidgetProviderVSphere.tsx @@ -0,0 +1,48 @@ +import { Text } from 'grommet'; +import { normalizeColor } from 'grommet/utils'; +import * as capvv1beta1 from 'model/services/mapi/capvv1beta1'; +import React, { useMemo } from 'react'; +import styled from 'styled-components'; +import OptionalValue from 'UI/Display/OptionalValue/OptionalValue'; + +const StyledLink = styled.a` + color: ${({ theme }) => normalizeColor('text-weak', theme)}; +`; + +interface IClusterDetailWidgetProviderAWSProps { + providerCluster: capvv1beta1.IVSphereCluster; +} + +const ClusterDetailWidgetProviderAWS: React.FC< + React.PropsWithChildren +> = ({ providerCluster }) => { + const vCenter = useMemo(() => { + if (!providerCluster) return undefined; + + return providerCluster.spec?.server ?? ''; + }, [providerCluster]); + + return ( + <> + vCenter + + {(value) => ( + + {value} + + + )} + + + ); +}; + +export default ClusterDetailWidgetProviderAWS; diff --git a/src/components/MAPI/clusters/ClusterDetail/__tests__/ClusterDetailWidgetControlPlaneNodes.tsx b/src/components/MAPI/clusters/ClusterDetail/__tests__/ClusterDetailWidgetControlPlaneNodes.tsx index 6322bde25f..dcb07b826f 100644 --- a/src/components/MAPI/clusters/ClusterDetail/__tests__/ClusterDetailWidgetControlPlaneNodes.tsx +++ b/src/components/MAPI/clusters/ClusterDetail/__tests__/ClusterDetailWidgetControlPlaneNodes.tsx @@ -65,7 +65,7 @@ describe('ClusterDetailWidgetControlPlaneNodes', () => { }) ); - expect(screen.getAllByLabelText('Loading...').length).toEqual(2); + expect(screen.getAllByLabelText('Loading...').length).toEqual(1); }); }); diff --git a/src/components/MAPI/clusters/CreateClusterAppBundles/schemaUtils.ts b/src/components/MAPI/clusters/CreateClusterAppBundles/schemaUtils.ts index e4d8413642..02da198b8b 100644 --- a/src/components/MAPI/clusters/CreateClusterAppBundles/schemaUtils.ts +++ b/src/components/MAPI/clusters/CreateClusterAppBundles/schemaUtils.ts @@ -319,19 +319,38 @@ const formPropsProviderGCP: Record = { const formPropsProviderVSphere: Record = { 0: { uiSchema: { - 'ui:order': ['cluster', 'controlPlane', '*'], + 'ui:order': ['global', '*'], + baseDomain: { + 'ui:widget': 'hidden', + }, cluster: { - 'ui:order': ['name', 'organization', '*'], - name: { - 'ui:widget': ClusterNameWidget, + 'ui:widget': 'hidden', + }, + global: { + metadata: { + 'ui:order': ['name', 'description', '*'], + name: { + 'ui:widget': ClusterNameWidget, + }, }, }, + managementCluster: { + 'ui:widget': 'hidden', + }, + provider: { + 'ui:widget': 'hidden', + }, + 'cluster-shared': { + 'ui:widget': 'hidden', + }, }, formData: (clusterName, organization) => { return { - cluster: { - name: clusterName, - organization, + global: { + metadata: { + name: clusterName, + organization, + }, }, }; }, diff --git a/src/components/MAPI/clusters/permissions/usePermissionsForCPNodes.ts b/src/components/MAPI/clusters/permissions/usePermissionsForCPNodes.ts index 5dae3a15b2..9841f1bf08 100644 --- a/src/components/MAPI/clusters/permissions/usePermissionsForCPNodes.ts +++ b/src/components/MAPI/clusters/permissions/usePermissionsForCPNodes.ts @@ -3,6 +3,7 @@ import { usePermissions } from 'MAPI/permissions/usePermissions'; import { hasPermission } from 'MAPI/permissions/utils'; import { Providers } from 'model/constants'; +// eslint-disable-next-line complexity export function usePermissionsForCPNodes( provider: PropertiesOf, namespace: string @@ -268,6 +269,45 @@ export function usePermissionsForCPNodes( ); break; + + case Providers.VSPHERE: + computed.canCreate = canCreateClusterApps; + computed.canDelete = canDeleteClusterApps; + computed.canUpdate = canUpdateClusterApps; + + computed.canGet = + hasPermission( + permissions, + namespace, + 'get', + 'cluster.x-k8s.io', + 'machines' + ) && + hasPermission( + permissions, + namespace, + 'get', + 'infrastructure.cluster.x-k8s.io', + 'vspheremachines' + ); + + computed.canList = + hasPermission( + permissions, + namespace, + 'list', + 'cluster.x-k8s.io', + 'machines' + ) && + hasPermission( + permissions, + namespace, + 'list', + 'infrastructure.cluster.x-k8s.io', + 'vspheremachines' + ); + + break; } return computed; diff --git a/src/components/MAPI/clusters/permissions/usePermissionsForClusters.ts b/src/components/MAPI/clusters/permissions/usePermissionsForClusters.ts index 68848394f1..c689610649 100644 --- a/src/components/MAPI/clusters/permissions/usePermissionsForClusters.ts +++ b/src/components/MAPI/clusters/permissions/usePermissionsForClusters.ts @@ -317,6 +317,45 @@ export function usePermissionsForClusters( ); break; + + case Providers.VSPHERE: + computed.canCreate = canCreateClusterApps; + computed.canDelete = canDeleteClusterApps; + computed.canUpdate = canUpdateClusterApps; + + computed.canGet = + hasPermission( + permissions, + namespace, + 'get', + 'cluster.x-k8s.io', + 'clusters' + ) && + hasPermission( + permissions, + namespace, + 'get', + 'infrastructure.cluster.x-k8s.io', + 'vsphereclusters' + ); + + computed.canList = + hasPermission( + permissions, + namespace, + 'list', + 'cluster.x-k8s.io', + 'clusters' + ) && + hasPermission( + permissions, + namespace, + 'list', + 'infrastructure.cluster.x-k8s.io', + 'vsphereclusters' + ); + + break; } return computed; diff --git a/src/components/MAPI/clusters/permissions/usePermissionsForControlPlanes.ts b/src/components/MAPI/clusters/permissions/usePermissionsForControlPlanes.ts index 84f5942b26..54728f466e 100644 --- a/src/components/MAPI/clusters/permissions/usePermissionsForControlPlanes.ts +++ b/src/components/MAPI/clusters/permissions/usePermissionsForControlPlanes.ts @@ -28,6 +28,7 @@ export function usePermissionsForControlPlanes( switch (provider) { case Providers.CAPA: case Providers.CAPZ: + case Providers.VSPHERE: computed.canCreate = canCreateClusterApps; computed.canDelete = canDeleteClusterApps; computed.canUpdate = canUpdateClusterApps; diff --git a/src/components/MAPI/clusters/utils.ts b/src/components/MAPI/clusters/utils.ts index 53ff130e98..79fd9936b5 100644 --- a/src/components/MAPI/clusters/utils.ts +++ b/src/components/MAPI/clusters/utils.ts @@ -25,6 +25,7 @@ import { Constants, Providers } from 'model/constants'; import * as capav1beta2 from 'model/services/mapi/capav1beta2'; import * as capgv1beta1 from 'model/services/mapi/capgv1beta1'; import * as capiv1beta1 from 'model/services/mapi/capiv1beta1'; +import * as capvv1beta1 from 'model/services/mapi/capvv1beta1'; import * as capzv1beta1 from 'model/services/mapi/capzv1beta1'; import * as corev1 from 'model/services/mapi/corev1'; import * as infrav1alpha3 from 'model/services/mapi/infrastructurev1alpha3'; @@ -33,7 +34,7 @@ import * as releasev1alpha1 from 'model/services/mapi/releasev1alpha1'; import { filterLabels } from 'model/stores/cluster/utils'; import { mutate } from 'swr'; import ErrorReporter from 'utils/errors/ErrorReporter'; -import { parseRFC822DateFormat } from 'utils/helpers'; +import { convertMiBtoBytes, parseRFC822DateFormat } from 'utils/helpers'; import { HttpClientFactory } from 'utils/hooks/useHttpClientFactory'; import { IOAuth2Provider } from 'utils/OAuth2/OAuth2'; import { compare } from 'utils/semver'; @@ -56,21 +57,42 @@ export function getWorkerNodesCPU( nodePoolsWithProviderNodePools?: IProviderNodePoolForNodePool[], machineTypes?: Record ) { - if (!nodePoolsWithProviderNodePools || !machineTypes) return undefined; + if (!nodePoolsWithProviderNodePools) return undefined; + + if (machineTypes) { + let count = 0; + + for (const { + nodePool, + providerNodePool, + } of nodePoolsWithProviderNodePools) { + if (!providerNodePool) return -1; + + const instanceType = + getProviderNodePoolMachineTypes(providerNodePool)?.primary ?? ''; + const machineTypeProperties = machineTypes[instanceType]; + if (!machineTypeProperties) return -1; + + const readyReplicas = getNodePoolReadyReplicas(nodePool); + + count += machineTypeProperties.cpu * readyReplicas; + } + + return count; + } let count = 0; for (const { nodePool, providerNodePool } of nodePoolsWithProviderNodePools) { if (!providerNodePool) return -1; - const instanceType = - getProviderNodePoolMachineTypes(providerNodePool)?.primary ?? ''; - const machineTypeProperties = machineTypes[instanceType]; - if (!machineTypeProperties) return -1; + if (providerNodePool.kind === capvv1beta1.VSphereMachineTemplate) { + const readyReplicas = getNodePoolReadyReplicas(nodePool); + const numCPUs = providerNodePool.spec?.template?.spec?.numCPUs; + if (typeof numCPUs === 'undefined') return -1; - const readyReplicas = getNodePoolReadyReplicas(nodePool); - - count += machineTypeProperties.cpu * readyReplicas; + count += numCPUs * readyReplicas; + } } return count; @@ -80,21 +102,42 @@ export function getWorkerNodesMemory( nodePoolsWithProviderNodePools?: IProviderNodePoolForNodePool[], machineTypes?: Record ) { - if (!nodePoolsWithProviderNodePools || !machineTypes) return undefined; + if (!nodePoolsWithProviderNodePools) return undefined; + + if (machineTypes) { + let count = 0; + + for (const { + nodePool, + providerNodePool, + } of nodePoolsWithProviderNodePools) { + if (!providerNodePool) return -1; + + const instanceType = + getProviderNodePoolMachineTypes(providerNodePool)?.primary ?? ''; + const machineTypeProperties = machineTypes[instanceType]; + if (!machineTypeProperties) return -1; + + const readyReplicas = getNodePoolReadyReplicas(nodePool); + + count += machineTypeProperties.memory * readyReplicas; + } + + return count; + } let count = 0; for (const { nodePool, providerNodePool } of nodePoolsWithProviderNodePools) { if (!providerNodePool) return -1; - const instanceType = - getProviderNodePoolMachineTypes(providerNodePool)?.primary ?? ''; - const machineTypeProperties = machineTypes[instanceType]; - if (!machineTypeProperties) return -1; + if (providerNodePool.kind === capvv1beta1.VSphereMachineTemplate) { + const readyReplicas = getNodePoolReadyReplicas(nodePool); + const memoryMiB = providerNodePool.spec?.template?.spec?.memoryMiB; + if (typeof memoryMiB === 'undefined') return -1; - const readyReplicas = getNodePoolReadyReplicas(nodePool); - - count += machineTypeProperties.memory * readyReplicas; + count += convertMiBtoBytes(memoryMiB) * readyReplicas; + } } return count; diff --git a/src/components/MAPI/organizations/OrganizationDetailGeneral/utils.ts b/src/components/MAPI/organizations/OrganizationDetailGeneral/utils.ts index 211b21052f..fd36b966e9 100644 --- a/src/components/MAPI/organizations/OrganizationDetailGeneral/utils.ts +++ b/src/components/MAPI/organizations/OrganizationDetailGeneral/utils.ts @@ -24,12 +24,14 @@ import { GenericResponse } from 'model/clients/GenericResponse'; import * as capav1beta2 from 'model/services/mapi/capav1beta2'; import * as capgv1beta1 from 'model/services/mapi/capgv1beta1'; import * as capiv1beta1 from 'model/services/mapi/capiv1beta1'; +import * as capvv1beta1 from 'model/services/mapi/capvv1beta1'; import * as capzv1beta1 from 'model/services/mapi/capzv1beta1'; import * as infrav1alpha3 from 'model/services/mapi/infrastructurev1alpha3'; import * as metav1 from 'model/services/mapi/metav1'; import * as releasev1alpha1 from 'model/services/mapi/releasev1alpha1'; import * as ui from 'UI/Display/Organizations/types'; import ErrorReporter from 'utils/errors/ErrorReporter'; +import { convertMiBtoBytes } from 'utils/helpers'; import { HttpClientFactory } from 'utils/hooks/useHttpClientFactory'; import { IOAuth2Provider } from 'utils/OAuth2/OAuth2'; import { compare } from 'utils/semver'; @@ -95,7 +97,7 @@ async function fetchSingleClusterSummary( cluster ); - appendControlPlaneNodeStats(cpNodes, machineTypes, summary); + appendControlPlaneNodeStats(summary, cpNodes, machineTypes); } catch (err) { ErrorReporter.getInstance().notify(err as Error); } @@ -110,7 +112,7 @@ async function fetchSingleClusterSummary( cluster.metadata.namespace ); - appendNodePoolsStats(nodePoolList.items, summary); + appendNodePoolsStats(summary, nodePoolList.items); const providerSpecificNodePools = await fetchProviderNodePoolsForNodePools( @@ -125,9 +127,9 @@ async function fetchSingleClusterSummary( ); appendProviderNodePoolsStats( + summary, nodePoolsWithProviderNodePools, - machineTypes, - summary + machineTypes ); } catch (err) { ErrorReporter.getInstance().notify(err as Error); @@ -141,10 +143,11 @@ function fetchSingleClusterSummaryKey(cluster: capiv1beta1.ICluster): string { return `fetchSingleClusterSummary/${cluster.metadata.namespace}/${cluster.metadata.name}`; } +// eslint-disable-next-line complexity function appendControlPlaneNodeStats( + summary: ui.IOrganizationDetailClustersSummary, controlPlaneNodes: ControlPlaneNode[], - machineTypes: Record, - summary: ui.IOrganizationDetailClustersSummary + machineTypes?: Record ) { summary.nodesCount = 0; @@ -197,26 +200,43 @@ function appendControlPlaneNodeStats( } } } + if (machineTypes) { + for (let i = 0; i < summary.nodesCount; i++) { + const instanceType = instanceTypes[i] ?? instanceTypes[0]; - for (let i = 0; i < summary.nodesCount; i++) { - const instanceType = instanceTypes[i] ?? instanceTypes[0]; + const machineTypeProperties = machineTypes[instanceType]; + if (!machineTypeProperties) { + throw new Error('Invalid machine type.'); + } - const machineTypeProperties = machineTypes[instanceType]; - if (!machineTypeProperties) { - throw new Error('Invalid machine type.'); - } + summary.nodesCPU ??= 0; + summary.nodesCPU += machineTypeProperties.cpu; - summary.nodesCPU ??= 0; - summary.nodesCPU += machineTypeProperties.cpu; + summary.nodesMemory ??= 0; + summary.nodesMemory += machineTypeProperties.memory; + } + } else { + for (const cpNode of controlPlaneNodes) { + if (cpNode.kind === capvv1beta1.VSphereMachine) { + const numCPUs = cpNode.spec?.numCPUs; + if (numCPUs) { + summary.nodesCPU ??= 0; + summary.nodesCPU += numCPUs; + } - summary.nodesMemory ??= 0; - summary.nodesMemory += machineTypeProperties.memory; + const memoryMiB = cpNode.spec?.memoryMiB; + if (memoryMiB) { + summary.nodesMemory ??= 0; + summary.nodesMemory += convertMiBtoBytes(memoryMiB); + } + } + } } } function appendNodePoolsStats( - nodePools: NodePool[], - summary: ui.IOrganizationDetailClustersSummary + summary: ui.IOrganizationDetailClustersSummary, + nodePools: NodePool[] ) { for (const nodePool of nodePools) { summary.workerNodesCount ??= 0; @@ -225,29 +245,44 @@ function appendNodePoolsStats( } function appendProviderNodePoolsStats( + summary: ui.IOrganizationDetailClustersSummary, nodePoolsWithProviderNodePools: IProviderNodePoolForNodePool[], - machineTypes: Record, - summary: ui.IOrganizationDetailClustersSummary + machineTypes?: Record ) { for (const { nodePool, providerNodePool } of nodePoolsWithProviderNodePools) { const readyReplicas = getNodePoolReadyReplicas(nodePool); if (!readyReplicas || !providerNodePool) continue; - const instanceType = - getProviderNodePoolMachineTypes(providerNodePool)?.primary; + if (machineTypes) { + const instanceType = + getProviderNodePoolMachineTypes(providerNodePool)?.primary; - const machineTypeProperties = instanceType - ? machineTypes[instanceType] - : null; - if (!machineTypeProperties) { - throw new Error('Invalid machine type.'); - } + const machineTypeProperties = instanceType + ? machineTypes[instanceType] + : null; + if (!machineTypeProperties) { + throw new Error('Invalid machine type.'); + } + + summary.workerNodesCPU ??= 0; + summary.workerNodesCPU += machineTypeProperties.cpu * readyReplicas; - summary.workerNodesCPU ??= 0; - summary.workerNodesCPU += machineTypeProperties.cpu * readyReplicas; + summary.workerNodesMemory ??= 0; + summary.workerNodesMemory += machineTypeProperties.memory * readyReplicas; + } else if (providerNodePool.kind === capvv1beta1.VSphereMachineTemplate) { + const numCPUs = providerNodePool.spec?.template?.spec?.numCPUs; + if (numCPUs) { + summary.workerNodesCPU ??= 0; + summary.workerNodesCPU += numCPUs * readyReplicas; + } - summary.workerNodesMemory ??= 0; - summary.workerNodesMemory += machineTypeProperties.memory * readyReplicas; + const memoryMiB = providerNodePool.spec?.template?.spec?.memoryMiB; + if (memoryMiB) { + summary.workerNodesMemory ??= 0; + summary.workerNodesMemory += + convertMiBtoBytes(memoryMiB) * readyReplicas; + } + } } } diff --git a/src/components/MAPI/releases/utils.ts b/src/components/MAPI/releases/utils.ts index 4b4f00402c..7b193c27c4 100644 --- a/src/components/MAPI/releases/utils.ts +++ b/src/components/MAPI/releases/utils.ts @@ -169,7 +169,7 @@ export function getPreviewReleaseVersions( } export function normalizeReleaseVersion(version: string): string { - const normalizedVersion = version.replace(/^(aws-|azure-)/, ''); + const normalizedVersion = version.replace(/^(aws-|azure-|vsphere-)/, ''); if (normalizedVersion.toLowerCase().startsWith('v')) { return normalizedVersion.substring(1); } diff --git a/src/components/MAPI/types.ts b/src/components/MAPI/types.ts index 9a7013d590..fc43d4126f 100644 --- a/src/components/MAPI/types.ts +++ b/src/components/MAPI/types.ts @@ -2,6 +2,7 @@ import * as capav1beta2 from 'model/services/mapi/capav1beta2'; import * as capgv1beta1 from 'model/services/mapi/capgv1beta1'; import * as capiexpv1alpha3 from 'model/services/mapi/capiv1alpha3/exp'; import * as capiv1beta1 from 'model/services/mapi/capiv1beta1'; +import * as capvv1beta1 from 'model/services/mapi/capvv1beta1'; import * as capzexpv1alpha3 from 'model/services/mapi/capzv1alpha3/exp'; import * as capzv1beta1 from 'model/services/mapi/capzv1beta1'; import * as gscorev1alpha1 from 'model/services/mapi/gscorev1alpha1'; @@ -15,6 +16,7 @@ export type ControlPlaneNode = | capav1beta2.IAWSManagedControlPlane | capgv1beta1.IGCPMachineTemplate | capiv1beta1.IMachine + | capvv1beta1.IVSphereMachine | capzv1beta1.IAzureMachine | capzv1beta1.IAzureMachineTemplate | infrav1alpha3.IAWSControlPlane @@ -27,6 +29,7 @@ export type ControlPlaneNodeList = | capav1beta2.IAWSMachineTemplateList | capav1beta2.IAWSManagedControlPlaneList | capgv1beta1.IGCPMachineTemplateList + | capvv1beta1.IVSphereMachineTemplateList | capiv1beta1.IMachineList; export type Cluster = capiv1beta1.ICluster; @@ -39,6 +42,7 @@ export type ProviderCluster = | capav1beta2.IAWSCluster | capav1beta2.IAWSManagedCluster | capgv1beta1.IGCPCluster + | capvv1beta1.IVSphereCluster | undefined; export type ProviderClusterList = @@ -62,6 +66,7 @@ export type ProviderNodePool = | capav1beta2.IAWSMachinePool | capav1beta2.IAWSManagedMachinePool | capgv1beta1.IGCPMachineTemplate + | capvv1beta1.IVSphereMachineTemplate | capzv1beta1.IAzureMachineTemplate | capzexpv1alpha3.IAzureMachinePool | capzv1beta1.IAzureMachinePool @@ -74,7 +79,8 @@ export type ProviderNodePoolList = | infrav1alpha3.IAWSMachineDeploymentList | capav1beta2.IAWSMachinePoolList | capav1beta2.IAWSManagedMachinePoolList - | capgv1beta1.IGCPMachineTemplateList; + | capgv1beta1.IGCPMachineTemplateList + | capvv1beta1.IVSphereMachineTemplateList; export type ProviderCredential = | legacyCredentials.ICredential diff --git a/src/components/MAPI/utils.ts b/src/components/MAPI/utils.ts index 0007672cde..acc7342c24 100644 --- a/src/components/MAPI/utils.ts +++ b/src/components/MAPI/utils.ts @@ -5,6 +5,7 @@ import * as capav1beta2 from 'model/services/mapi/capav1beta2'; import * as capgv1beta1 from 'model/services/mapi/capgv1beta1'; import * as capiexpv1alpha3 from 'model/services/mapi/capiv1alpha3/exp'; import * as capiv1beta1 from 'model/services/mapi/capiv1beta1'; +import * as capvv1beta1 from 'model/services/mapi/capvv1beta1'; import * as capzexpv1alpha3 from 'model/services/mapi/capzv1alpha3/exp'; import * as capzv1beta1 from 'model/services/mapi/capzv1beta1'; import * as infrav1alpha3 from 'model/services/mapi/infrastructurev1alpha3'; @@ -33,7 +34,7 @@ export interface IMachineType { memory: number; } -export function getMachineTypes(): Record { +export function getMachineTypes(): Record | undefined { const machineTypes: Record = {}; if (window.config.awsCapabilitiesJSON) { @@ -75,7 +76,7 @@ export function getMachineTypes(): Record { } } - return machineTypes; + return Object.keys(machineTypes).length > 0 ? machineTypes : undefined; } export function compareNodePools(a: NodePool, b: NodePool) { @@ -147,6 +148,7 @@ export async function fetchNodePoolListForCluster( switch (true) { case kind === capgv1beta1.GCPCluster: + case kind === capvv1beta1.VSphereCluster: list = await capiv1beta1.getMachineDeploymentList( httpClientFactory(), auth, @@ -255,6 +257,7 @@ export function fetchNodePoolListForClusterKey( switch (true) { case kind === capgv1beta1.GCPCluster: + case kind === capvv1beta1.VSphereCluster: return capiv1beta1.getMachineDeploymentListKey({ labelSelector: { matchingLabels: { @@ -356,6 +359,14 @@ export async function fetchProviderNodePoolForNodePool( infrastructureRef.name ); + case kind === capvv1beta1.VSphereMachineTemplate: + return capvv1beta1.getVSphereMachineTemplate( + httpClientFactory(), + auth, + nodePool.metadata.namespace!, + infrastructureRef.name + ); + case kind === capzv1beta1.AzureMachineTemplate: return capzv1beta1.getAzureMachineTemplate( httpClientFactory(), @@ -781,6 +792,47 @@ export async function fetchControlPlaneNodesForCluster( return cpNodes; } + case kind === capvv1beta1.VSphereCluster && + apiGroup === capvv1beta1.ApiGroup: { + const [capvCP, machineCP] = await Promise.allSettled([ + capvv1beta1.getVSphereMachineList(httpClientFactory(), auth, { + labelSelector: { + matchingLabels: { + [capiv1beta1.labelClusterName]: cluster.metadata.name, + [capiv1beta1.labelMachineControlPlane]: '', + }, + }, + namespace: cluster.metadata.namespace, + }), + capiv1beta1.getMachineList(httpClientFactory(), auth, { + labelSelector: { + matchingLabels: { + [capiv1beta1.labelClusterName]: cluster.metadata.name, + [capiv1beta1.labelMachineControlPlane]: '', + }, + }, + namespace: cluster.metadata.namespace, + }), + ]); + + if (capvCP.status === 'rejected' && machineCP.status === 'rejected') { + return Promise.reject(capvCP.reason); + } + + let cpNodes: ControlPlaneNode[] = []; + if (capvCP.status === 'fulfilled' && capvCP.value.items.length > 0) { + cpNodes = [...cpNodes, ...capvCP.value.items]; + } + if ( + machineCP.status === 'fulfilled' && + machineCP.value.items.length > 0 + ) { + cpNodes = [...cpNodes, ...machineCP.value.items]; + } + + return cpNodes; + } + case kind === capzv1beta1.AzureCluster && hasClusterAppLabel(cluster): { const [capzCP, machineCP] = await Promise.allSettled([ capzv1beta1.getAzureMachineTemplateList(httpClientFactory(), auth, { @@ -932,6 +984,17 @@ export function fetchControlPlaneNodesForClusterKey( namespace: cluster.metadata.namespace, }); + case kind === capvv1beta1.VSphereCluster && + apiGroup === capvv1beta1.ApiGroup: + return capvv1beta1.getVSphereMachineTemplateListKey({ + labelSelector: { + matchingLabels: { + [capiv1beta1.labelCluster]: cluster.metadata.name, + }, + }, + namespace: cluster.metadata.namespace, + }); + case kind === capzv1beta1.AzureCluster && hasClusterAppLabel(cluster): return capzv1beta1.getAzureMachineTemplateListKey({ labelSelector: { @@ -1010,6 +1073,15 @@ export async function fetchProviderClusterForCluster( infrastructureRef.name ); + case kind === capvv1beta1.VSphereCluster && + apiGroup === capvv1beta1.ApiGroup: + return capvv1beta1.getVSphereCluster( + httpClientFactory(), + auth, + cluster.metadata.namespace!, + infrastructureRef.name + ); + case kind === capzv1beta1.AzureCluster: return capzv1beta1.getAzureCluster( httpClientFactory(), @@ -1059,6 +1131,13 @@ export function fetchProviderClusterForClusterKey(cluster: Cluster) { infrastructureRef.name ); + case kind === capvv1beta1.VSphereCluster && + apiGroup === capvv1beta1.ApiGroup: + return capvv1beta1.getVSphereClusterKey( + cluster.metadata.namespace!, + infrastructureRef.name + ); + case kind === capzv1beta1.AzureCluster: return capzv1beta1.getAzureClusterKey( cluster.metadata.namespace!, @@ -1769,12 +1848,28 @@ function isCAPZCluster(cluster: Cluster): boolean { return cluster.spec?.infrastructureRef?.kind === capzv1beta1.AzureCluster; } +function isVSphereCluster(cluster: Cluster): boolean { + return cluster.spec?.infrastructureRef?.kind === capvv1beta1.VSphereCluster; +} + export function isNodePoolMngmtReadOnly(cluster: Cluster): boolean { return hasClusterAppLabel(cluster) || isResourceImported(cluster); } export function supportsNodePoolAutoscaling(cluster: Cluster): boolean { - return !(isCAPGCluster(cluster) || isCAPZCluster(cluster)); + return !( + isCAPGCluster(cluster) || + isCAPZCluster(cluster) || + isVSphereCluster(cluster) + ); +} + +export function supportsMachineTypes(cluster: Cluster): boolean { + return !isVSphereCluster(cluster); +} + +export function supportsAvailabilityZones(cluster: Cluster): boolean { + return !isVSphereCluster(cluster); } export function supportsNonExpMachinePools(cluster: Cluster): boolean { diff --git a/src/components/MAPI/workernodes/ClusterDetailWorkerNodes.tsx b/src/components/MAPI/workernodes/ClusterDetailWorkerNodes.tsx index 5a2d7fa106..1a7a9439a5 100644 --- a/src/components/MAPI/workernodes/ClusterDetailWorkerNodes.tsx +++ b/src/components/MAPI/workernodes/ClusterDetailWorkerNodes.tsx @@ -19,6 +19,8 @@ import { isNodePoolMngmtReadOnly, isResourceImported, isResourceManagedByGitOps, + supportsAvailabilityZones, + supportsMachineTypes, supportsNodePoolAutoscaling, supportsNonExpMachinePools, supportsReleases, @@ -27,6 +29,7 @@ import { GenericResponseError } from 'model/clients/GenericResponseError'; import { ProviderFlavors, Providers } from 'model/constants'; import * as capav1beta2 from 'model/services/mapi/capav1beta2'; import * as capiv1beta1 from 'model/services/mapi/capiv1beta1'; +import * as capvv1beta1 from 'model/services/mapi/capvv1beta1'; import * as capzexpv1alpha3 from 'model/services/mapi/capzv1alpha3/exp'; import * as capzv1beta1 from 'model/services/mapi/capzv1beta1'; import * as infrav1alpha3 from 'model/services/mapi/infrastructurev1alpha3'; @@ -37,7 +40,7 @@ import React from 'react'; import { Breadcrumb } from 'react-breadcrumbs'; import { useLocation, useParams } from 'react-router'; import { TransitionGroup } from 'react-transition-group'; -import { COPYABLE_PADDING } from 'shared/Copyable'; +import Copyable, { COPYABLE_PADDING } from 'shared/Copyable'; import DocumentTitle from 'shared/DocumentTitle'; import styled from 'styled-components'; import { CODE_CHAR_WIDTH, CODE_PADDING } from 'styles'; @@ -47,8 +50,11 @@ import Button from 'UI/Controls/Button'; import CLIGuidesList from 'UI/Display/MAPI/CLIGuide/CLIGuidesList'; import { NodePoolGridRow } from 'UI/Display/MAPI/workernodes/styles'; import WorkerNodesNodePoolListPlaceholder from 'UI/Display/MAPI/workernodes/WorkerNodesNodePoolListPlaceholder'; +import OptionalValue from 'UI/Display/OptionalValue/OptionalValue'; +import Truncated from 'UI/Util/Truncated'; import ErrorReporter from 'utils/errors/ErrorReporter'; import { FlashMessage, messageTTL, messageType } from 'utils/flashMessage'; +import { getTruncationParams } from 'utils/helpers'; import { useHttpClientFactory } from 'utils/hooks/useHttpClientFactory'; import DeleteNodePoolGuide from './guides/DeleteNodePoolGuide'; @@ -68,7 +74,96 @@ const LOADING_COMPONENTS = new Array(4).fill(0); export const MAX_NAME_LENGTH = 10; export const MAX_NAME_LENGTH_LONG = 20; -export function getAdditionalColumns( +export function getAdditionalColumns1( + cluster: Cluster +): IWorkerNodesAdditionalColumn[] { + const infrastructureRef = cluster?.spec?.infrastructureRef; + if (!infrastructureRef) return []; + + const { kind, apiVersion } = infrastructureRef; + const apiGroup = getApiGroupFromApiVersion(apiVersion); + + switch (true) { + case kind === capvv1beta1.VSphereCluster && + apiGroup === capvv1beta1.ApiGroup: + return [ + { + title: 'Resource pool', + render: (_, providerNodePool) => { + const resourcePool = providerNodePool + ? (providerNodePool as capvv1beta1.IVSphereMachineTemplate)?.spec + ?.template.spec.resourcePool ?? '' + : undefined; + + return ( + + {(value) => {value}} + + ); + }, + }, + { + title: 'Datacenter', + render: (_, providerNodePool) => { + const datacenter = providerNodePool + ? (providerNodePool as capvv1beta1.IVSphereMachineTemplate)?.spec + ?.template.spec.datacenter ?? '' + : undefined; + + return ( + + {(value) => {value}} + + ); + }, + }, + { + title: 'vCenter', + render: (_, providerNodePool) => { + const vCenter = providerNodePool + ? (providerNodePool as capvv1beta1.IVSphereMachineTemplate)?.spec + ?.template.spec.server ?? '' + : undefined; + + return ( + + {(value) => ( + + + {value} + + + )} + + ); + }, + }, + { + title: 'Datastore', + render: (_, providerNodePool) => { + const datastore = providerNodePool + ? (providerNodePool as capvv1beta1.IVSphereMachineTemplate).spec + ?.template.spec.datastore ?? '' + : undefined; + + return ( + + {(value) => {value}} + + ); + }, + }, + ]; + + default: + return []; + } +} + +export function getAdditionalColumns2( cluster: Cluster ): IWorkerNodesAdditionalColumn[] { const infrastructureRef = cluster?.spec?.infrastructureRef; @@ -169,21 +264,30 @@ function getNameColumnWidth(nameLength: number, maxNameLength: number) { } const Header = styled(Box)<{ - additionalColumnsCount?: number; + additionalColumnsCount1?: number; + additionalColumnsCount2?: number; nameColumnWidth?: number; displayDescriptionColumn?: boolean; + displayMachineTypeColumn?: boolean; + displayAvailabilityZonesColumn?: boolean; displayMenuColumn?: boolean; }>` ${({ - additionalColumnsCount, + additionalColumnsCount1, + additionalColumnsCount2, nameColumnWidth, displayDescriptionColumn, + displayMachineTypeColumn, + displayAvailabilityZonesColumn, displayMenuColumn, }) => NodePoolGridRow( - additionalColumnsCount, + additionalColumnsCount1, + additionalColumnsCount2, nameColumnWidth, displayDescriptionColumn, + displayMachineTypeColumn, + displayAvailabilityZonesColumn, displayMenuColumn )} @@ -192,21 +296,30 @@ const Header = styled(Box)<{ `; const ColumnInfo = styled(Box)<{ - additionalColumnsCount?: number; + additionalColumnsCount1?: number; + additionalColumnsCount2?: number; nameColumnWidth?: number; displayDescriptionColumn?: boolean; + displayMachineTypeColumn?: boolean; + displayAvailabilityZonesColumn?: boolean; displayMenuColumn?: boolean; }>` ${({ - additionalColumnsCount, + additionalColumnsCount1, + additionalColumnsCount2, nameColumnWidth, displayDescriptionColumn, + displayMachineTypeColumn, + displayAvailabilityZonesColumn, displayMenuColumn, }) => NodePoolGridRow( - additionalColumnsCount, + additionalColumnsCount1, + additionalColumnsCount2, nameColumnWidth, displayDescriptionColumn, + displayMachineTypeColumn, + displayAvailabilityZonesColumn, displayMenuColumn )} @@ -486,10 +599,16 @@ const ClusterDetailWorkerNodes: React.FC< maxNameLength ); - const additionalColumns = useMemo(() => { + const additionalColumns1 = useMemo(() => { + if (!cluster) return []; + + return getAdditionalColumns1(cluster); + }, [cluster]); + + const additionalColumns2 = useMemo(() => { if (!cluster) return []; - return getAdditionalColumns(cluster); + return getAdditionalColumns2(cluster); }, [cluster]); const [isCreateFormOpen, setIsCreateFormOpen] = useState( @@ -513,6 +632,9 @@ const ClusterDetailWorkerNodes: React.FC< }, [cluster]); const displayCGroupsColumn = providerFlavor === ProviderFlavors.VINTAGE; + const displayMachineTypeColumn = cluster && supportsMachineTypes(cluster); + const displayAvailabilityZonesColumn = + cluster && supportsAvailabilityZones(cluster); const hideNodePoolAutoscalingColumns = cluster && !supportsNodePoolAutoscaling(cluster); const displayMenuColumn = useMemo(() => { @@ -553,13 +675,18 @@ const ClusterDetailWorkerNodes: React.FC< {!hasNoNodePools && cluster && ( @@ -570,10 +697,15 @@ const ClusterDetailWorkerNodes: React.FC< {displayDescriptionColumn && } {/* Machine type column placeholder */} - + {displayMachineTypeColumn && } {/* Availability zones column placeholder */} - + {displayAvailabilityZonesColumn && } + + {/* Additional columns placeholders */} + {additionalColumns1.map((column) => ( + + ))} {/* CGroups column placeholder */} {displayCGroupsColumn && } @@ -593,7 +725,7 @@ const ClusterDetailWorkerNodes: React.FC< {/* Additional columns placeholders */} - {additionalColumns.map((column) => ( + {additionalColumns2.map((column) => ( ))} @@ -601,13 +733,18 @@ const ClusterDetailWorkerNodes: React.FC< {displayMenuColumn && }
@@ -622,16 +759,27 @@ const ClusterDetailWorkerNodes: React.FC< Description )} - - - {formatMachineTypeColumnTitle(provider)} - - - - - {formatAZsColumnTitle(provider)} - - + {displayMachineTypeColumn && ( + + + {formatMachineTypeColumnTitle(provider)} + + + )} + {displayAvailabilityZonesColumn && ( + + + {formatAZsColumnTitle(provider)} + + + )} + {additionalColumns1.map((column) => ( + + + {column.title} + + + ))} {displayCGroupsColumn && ( CGroups @@ -654,7 +802,7 @@ const ClusterDetailWorkerNodes: React.FC< Current - {additionalColumns.map((column) => ( + {additionalColumns2.map((column) => ( {column.title} @@ -670,13 +818,18 @@ const ClusterDetailWorkerNodes: React.FC< LOADING_COMPONENTS.map((_, idx) => ( @@ -699,8 +852,13 @@ const ClusterDetailWorkerNodes: React.FC< ` ${({ - additionalColumnsCount, + additionalColumnsCount1, + additionalColumnsCount2, nameColumnWidth, displayDescriptionColumn, + displayMachineTypeColumn, + displayAvailabilityZonesColumn, displayMenuColumn, }) => NodePoolGridRow( - additionalColumnsCount, + additionalColumnsCount1, + additionalColumnsCount2, nameColumnWidth, displayDescriptionColumn, + displayMachineTypeColumn, + displayAvailabilityZonesColumn, displayMenuColumn )} `; @@ -100,15 +109,19 @@ interface IWorkerNodesNodePoolItemProps maxNameLength?: number; nodePool?: NodePool; providerNodePool?: ProviderNodePool | null; - additionalColumns?: IWorkerNodesAdditionalColumn[]; + additionalColumns1?: IWorkerNodesAdditionalColumn[]; + additionalColumns2?: IWorkerNodesAdditionalColumn[]; readOnly?: boolean; canUpdateNodePools?: boolean; canDeleteNodePools?: boolean; nameColumnWidth?: number; displayDescription?: boolean; + displayMachineType?: boolean; + displayAvailabilityZones?: boolean; displayCGroupsVersion?: boolean; displayMenuColumn?: boolean; flatcarContainerLinuxVersion?: string; + hideNodePoolMachineType?: boolean; hideNodePoolAutoscaling?: boolean; } @@ -118,13 +131,16 @@ const WorkerNodesNodePoolItem: React.FC< > = ({ nodePool, providerNodePool, - additionalColumns, + additionalColumns1, + additionalColumns2, readOnly, canUpdateNodePools, canDeleteNodePools, nameColumnWidth, maxNameLength = MAX_NAME_LENGTH, displayDescription = true, + displayMachineType = true, + displayAvailabilityZones = true, displayCGroupsVersion = true, displayMenuColumn = true, flatcarContainerLinuxVersion, @@ -267,13 +283,16 @@ const WorkerNodesNodePoolItem: React.FC< @@ -333,30 +352,39 @@ const WorkerNodesNodePoolItem: React.FC< )} {!isDeleting && !isEditingDescription && ( <> - - - - {(value) => ( - - - - )} - - + {displayMachineType && ( + + )} + {displayAvailabilityZones && ( + + + {(value) => ( + + + + )} + + + )} + {additionalColumns1?.map((column) => ( + + {column.render(nodePool, providerNodePool ?? undefined)} + + ))} {displayCGroupsVersion && ( @@ -450,7 +478,7 @@ const WorkerNodesNodePoolItem: React.FC< - {additionalColumns?.map((column) => ( + {additionalColumns2?.map((column) => ( {column.render(nodePool, providerNodePool ?? undefined)} diff --git a/src/components/MAPI/workernodes/__tests__/WorkerNodesNodePoolItem.tsx b/src/components/MAPI/workernodes/__tests__/WorkerNodesNodePoolItem.tsx index d7f72460e1..6ea415b0cb 100644 --- a/src/components/MAPI/workernodes/__tests__/WorkerNodesNodePoolItem.tsx +++ b/src/components/MAPI/workernodes/__tests__/WorkerNodesNodePoolItem.tsx @@ -18,7 +18,10 @@ import * as infrav1alpha3Mocks from 'test/mockHttpCalls/infrastructurev1alpha3'; import { getComponentWithStore } from 'test/renderUtils'; import TestOAuth2 from 'utils/OAuth2/TestOAuth2'; -import { getAdditionalColumns } from '../ClusterDetailWorkerNodes'; +import { + getAdditionalColumns1, + getAdditionalColumns2, +} from '../ClusterDetailWorkerNodes'; import WorkerNodesNodePoolItem from '../WorkerNodesNodePoolItem'; function getComponent( @@ -201,7 +204,10 @@ describe('WorkerNodesNodePoolItem on Azure', () => { getComponent({ nodePool: capiexpv1alpha3Mocks.randomCluster1MachinePool1, providerNodePool: capzexpv1alpha3Mocks.randomCluster1AzureMachinePool1, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomCluster1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomCluster1 ), }) @@ -227,7 +233,10 @@ describe('WorkerNodesNodePoolItem on Azure', () => { }, }, }, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomCluster1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomCluster1 ), }) @@ -645,7 +654,10 @@ describe('WorkerNodesNodePoolItem on AWS', () => { nodePool: capiv1beta1Mocks.randomClusterAWS1MachineDeployment1, providerNodePool: infrav1alpha3Mocks.randomClusterAWS1AWSMachineDeployment1, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomClusterAWS1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomClusterAWS1 ), }) @@ -669,7 +681,10 @@ describe('WorkerNodesNodePoolItem on AWS', () => { }, }, }, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomClusterAWS1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomClusterAWS1 ), }) @@ -934,7 +949,10 @@ describe('WorkerNodesNodePoolItem on CAPA', () => { getComponent({ nodePool: capiv1beta1Mocks.randomClusterCAPA1MachinePool1, providerNodePool: capav1beta2Mocks.randomClusterCAPA1AWSMachinePool, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomClusterCAPA1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomClusterCAPA1 ), }) @@ -959,7 +977,10 @@ describe('WorkerNodesNodePoolItem on CAPA', () => { getComponent({ nodePool: capiv1beta1Mocks.randomClusterCAPA1MachinePool1, providerNodePool: capav1beta2Mocks.randomClusterCAPA1AWSMachinePoolSpot, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomClusterCAPA1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomClusterCAPA1 ), }) @@ -1001,7 +1022,10 @@ describe('WorkerNodesNodePoolItem on CAPZ', () => { nodePool: capiv1beta1Mocks.randomClusterCAPZ1MachineDeployment1, providerNodePool: capzv1beta1Mocks.randomClusterCAPZ1AzureMachineTemplate, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomCluster1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomCluster1 ), }) @@ -1024,7 +1048,10 @@ describe('WorkerNodesNodePoolItem on CAPZ', () => { nodePool: capiv1beta1Mocks.randomClusterCAPZ1MachineDeployment1, providerNodePool: capzv1beta1Mocks.randomClusterCAPZ1AzureMachineTemplate, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomCluster1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomCluster1 ), }) @@ -1044,7 +1071,10 @@ describe('WorkerNodesNodePoolItem on CAPZ', () => { nodePool: capiv1beta1Mocks.randomClusterCAPZ1MachineDeployment1, providerNodePool: capzv1beta1Mocks.randomClusterCAPZ1AzureMachineTemplate, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomCluster1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomCluster1 ), }) @@ -1074,7 +1104,10 @@ describe('WorkerNodesNodePoolItem on CAPZ', () => { }, }, }, - additionalColumns: getAdditionalColumns( + additionalColumns1: getAdditionalColumns1( + capiv1beta1Mocks.randomCluster1 + ), + additionalColumns2: getAdditionalColumns2( capiv1beta1Mocks.randomCluster1 ), }) diff --git a/src/components/MAPI/workernodes/permissions/usePermissionsForNodePools.ts b/src/components/MAPI/workernodes/permissions/usePermissionsForNodePools.ts index 972f0ac65d..0cddadc0ed 100644 --- a/src/components/MAPI/workernodes/permissions/usePermissionsForNodePools.ts +++ b/src/components/MAPI/workernodes/permissions/usePermissionsForNodePools.ts @@ -353,6 +353,46 @@ export function usePermissionsForNodePools( ); break; + + case Providers.VSPHERE: + // Node pools are mutated through the cluster app's ConfigMap + computed.canCreate = canUpdateClusterApps; + computed.canDelete = canUpdateClusterApps; + computed.canUpdate = canUpdateClusterApps; + + computed.canGet = + hasPermission( + permissions, + namespace, + 'get', + 'cluster.x-k8s.io', + 'machinedeployments' + ) && + hasPermission( + permissions, + namespace, + 'get', + 'infrastructure.cluster.x-k8s.io', + 'vspheremachinetemplates' + ); + + computed.canList = + hasPermission( + permissions, + namespace, + 'list', + 'cluster.x-k8s.io', + 'machinedeployments' + ) && + hasPermission( + permissions, + namespace, + 'list', + 'infrastructure.cluster.x-k8s.io', + 'vspheremachinetemplates' + ); + + break; } return computed; diff --git a/src/components/UI/Display/MAPI/workernodes/styles.tsx b/src/components/UI/Display/MAPI/workernodes/styles.tsx index 2818bb861d..57608a0f02 100644 --- a/src/components/UI/Display/MAPI/workernodes/styles.tsx +++ b/src/components/UI/Display/MAPI/workernodes/styles.tsx @@ -1,9 +1,12 @@ import { css } from 'styled-components'; export const NodePoolGridRow = ( - extraColumnCount: number = 0, + extraColumnCount1: number = 0, + extraColumnCount2: number = 0, nameColumnWidth: number = 0, displayDescriptionColumn: boolean = true, + displayMachineTypeColumn: boolean = true, + displayAvailabilityZonesColumn: boolean = true, displayMenuColumn: boolean = true ) => css` display: grid; @@ -11,10 +14,11 @@ export const NodePoolGridRow = ( grid-template-columns: minmax(80px, ${nameColumnWidth}px) ${displayDescriptionColumn ? 'minmax(50px, 4fr)' : ''} - 4fr - 3fr + ${displayMachineTypeColumn ? '4fr' : ''} + ${displayAvailabilityZonesColumn ? '3fr' : ''} + ${extraColumnCount1 ? `repeat(${extraColumnCount1}, 2fr)` : ''} repeat(2, 2fr) - ${extraColumnCount ? `repeat(${extraColumnCount}, 2fr)` : ''} + ${extraColumnCount2 ? `repeat(${extraColumnCount2}, 2fr)` : ''} ${displayMenuColumn ? '1fr' : ''}; grid-template-rows: ${({ theme }) => theme.global.size.xxsmall}; justify-content: space-between; diff --git a/src/model/services/mapi/capvv1beta1/getVSphereCluster.ts b/src/model/services/mapi/capvv1beta1/getVSphereCluster.ts new file mode 100644 index 0000000000..5694c654a4 --- /dev/null +++ b/src/model/services/mapi/capvv1beta1/getVSphereCluster.ts @@ -0,0 +1,31 @@ +/** + * This file was automatically generated, PLEASE DO NOT MODIFY IT BY HAND. + */ + +import { IHttpClient } from 'model/clients/HttpClient'; +import { getResource } from 'model/services/mapi/generic/getResource'; +import * as k8sUrl from 'model/services/mapi/k8sUrl'; +import { IOAuth2Provider } from 'utils/OAuth2/OAuth2'; + +import { IVSphereCluster } from '.'; + +export function getVSphereCluster( + client: IHttpClient, + auth: IOAuth2Provider, + namespace: string, + name: string +) { + const url = k8sUrl.create({ + baseUrl: window.config.mapiEndpoint, + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1', + kind: 'vsphereclusters', + namespace, + name, + }); + + return getResource(client, auth, url.toString()); +} + +export function getVSphereClusterKey(namespace: string, name: string) { + return `getVSphereCluster/${namespace}/${name}`; +} diff --git a/src/model/services/mapi/capvv1beta1/getVSphereMachine.ts b/src/model/services/mapi/capvv1beta1/getVSphereMachine.ts new file mode 100644 index 0000000000..5cdadb573b --- /dev/null +++ b/src/model/services/mapi/capvv1beta1/getVSphereMachine.ts @@ -0,0 +1,31 @@ +/** + * This file was automatically generated, PLEASE DO NOT MODIFY IT BY HAND. + */ + +import { IHttpClient } from 'model/clients/HttpClient'; +import { getResource } from 'model/services/mapi/generic/getResource'; +import * as k8sUrl from 'model/services/mapi/k8sUrl'; +import { IOAuth2Provider } from 'utils/OAuth2/OAuth2'; + +import { IVSphereMachine } from '.'; + +export function getVSphereMachine( + client: IHttpClient, + auth: IOAuth2Provider, + namespace: string, + name: string +) { + const url = k8sUrl.create({ + baseUrl: window.config.mapiEndpoint, + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1', + kind: 'vspheremachines', + namespace, + name, + }); + + return getResource(client, auth, url.toString()); +} + +export function getVSphereMachineKey(namespace: string, name: string) { + return `getVSphereMachine/${namespace}/${name}`; +} diff --git a/src/model/services/mapi/capvv1beta1/getVSphereMachineList.ts b/src/model/services/mapi/capvv1beta1/getVSphereMachineList.ts new file mode 100644 index 0000000000..257b5781b0 --- /dev/null +++ b/src/model/services/mapi/capvv1beta1/getVSphereMachineList.ts @@ -0,0 +1,43 @@ +/** + * This file was automatically generated, PLEASE DO NOT MODIFY IT BY HAND. + */ + +import { IHttpClient } from 'model/clients/HttpClient'; +import { getListResource } from 'model/services/mapi/generic/getListResource'; +import * as k8sUrl from 'model/services/mapi/k8sUrl'; +import { IOAuth2Provider } from 'utils/OAuth2/OAuth2'; + +import { IVSphereMachineList } from '.'; + +export interface IGetVSphereMachineListOptions { + namespace?: string; + labelSelector?: k8sUrl.IK8sLabelSelector; +} + +export function getVSphereMachineList( + client: IHttpClient, + auth: IOAuth2Provider, + options?: IGetVSphereMachineListOptions +) { + const url = k8sUrl.create({ + baseUrl: window.config.mapiEndpoint, + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1', + kind: 'vspheremachines', + ...options, + }); + + return getListResource(client, auth, url.toString()); +} + +export function getVSphereMachineListKey( + options?: IGetVSphereMachineListOptions +) { + const url = k8sUrl.create({ + baseUrl: window.config.mapiEndpoint, + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1', + kind: 'vspheremachines', + ...options, + }); + + return url.toString(); +} diff --git a/src/model/services/mapi/capvv1beta1/getVSphereMachineTemplate.ts b/src/model/services/mapi/capvv1beta1/getVSphereMachineTemplate.ts new file mode 100644 index 0000000000..6bf5f8a4e2 --- /dev/null +++ b/src/model/services/mapi/capvv1beta1/getVSphereMachineTemplate.ts @@ -0,0 +1,31 @@ +/** + * This file was automatically generated, PLEASE DO NOT MODIFY IT BY HAND. + */ + +import { IHttpClient } from 'model/clients/HttpClient'; +import { getResource } from 'model/services/mapi/generic/getResource'; +import * as k8sUrl from 'model/services/mapi/k8sUrl'; +import { IOAuth2Provider } from 'utils/OAuth2/OAuth2'; + +import { IVSphereMachineTemplate } from '.'; + +export function getVSphereMachineTemplate( + client: IHttpClient, + auth: IOAuth2Provider, + namespace: string, + name: string +) { + const url = k8sUrl.create({ + baseUrl: window.config.mapiEndpoint, + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1', + kind: 'vspheremachinetemplates', + namespace, + name, + }); + + return getResource(client, auth, url.toString()); +} + +export function getVSphereMachineTemplateKey(namespace: string, name: string) { + return `getVSphereMachineTemplate/${namespace}/${name}`; +} diff --git a/src/model/services/mapi/capvv1beta1/getVSphereMachineTemplateList.ts b/src/model/services/mapi/capvv1beta1/getVSphereMachineTemplateList.ts new file mode 100644 index 0000000000..497881be01 --- /dev/null +++ b/src/model/services/mapi/capvv1beta1/getVSphereMachineTemplateList.ts @@ -0,0 +1,47 @@ +/** + * This file was automatically generated, PLEASE DO NOT MODIFY IT BY HAND. + */ + +import { IHttpClient } from 'model/clients/HttpClient'; +import { getListResource } from 'model/services/mapi/generic/getListResource'; +import * as k8sUrl from 'model/services/mapi/k8sUrl'; +import { IOAuth2Provider } from 'utils/OAuth2/OAuth2'; + +import { IVSphereMachineTemplateList } from '.'; + +export interface IGetVSphereMachineTemplateListOptions { + namespace?: string; + labelSelector?: k8sUrl.IK8sLabelSelector; +} + +export function getVSphereMachineTemplateList( + client: IHttpClient, + auth: IOAuth2Provider, + options?: IGetVSphereMachineTemplateListOptions +) { + const url = k8sUrl.create({ + baseUrl: window.config.mapiEndpoint, + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1', + kind: 'vspheremachinetemplates', + ...options, + }); + + return getListResource( + client, + auth, + url.toString() + ); +} + +export function getVSphereMachineTemplateListKey( + options?: IGetVSphereMachineTemplateListOptions +) { + const url = k8sUrl.create({ + baseUrl: window.config.mapiEndpoint, + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1', + kind: 'vspheremachinetemplates', + ...options, + }); + + return url.toString(); +} diff --git a/src/model/services/mapi/capvv1beta1/index.ts b/src/model/services/mapi/capvv1beta1/index.ts new file mode 100644 index 0000000000..aa2f18eb37 --- /dev/null +++ b/src/model/services/mapi/capvv1beta1/index.ts @@ -0,0 +1,6 @@ +export * from './types'; +export * from './getVSphereCluster'; +export * from './getVSphereMachineTemplate'; +export * from './getVSphereMachineTemplateList'; +export * from './getVSphereMachine'; +export * from './getVSphereMachineList'; diff --git a/src/model/services/mapi/capvv1beta1/types.ts b/src/model/services/mapi/capvv1beta1/types.ts new file mode 100644 index 0000000000..396c2ec13c --- /dev/null +++ b/src/model/services/mapi/capvv1beta1/types.ts @@ -0,0 +1,1330 @@ +/** + * This file was automatically generated, PLEASE DO NOT MODIFY IT BY HAND. + */ + +import * as metav1 from 'model/services/mapi/metav1'; + +export const ApiGroup = 'infrastructure.cluster.x-k8s.io'; + +export const ApiVersion = 'infrastructure.cluster.x-k8s.io/v1beta1'; + +export const VSphereCluster = 'VSphereCluster'; + +/** + * VSphereCluster is the Schema for the vsphereclusters API. + */ +export interface IVSphereCluster { + /** + * APIVersion defines the versioned schema of this representation of an object. + * Servers should convert recognized schemas to the latest internal value, and + * may reject unrecognized values. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + */ + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1'; + /** + * Kind is a string value representing the REST resource this object represents. + * Servers may infer this from the endpoint the client submits requests to. + * Cannot be updated. + * In CamelCase. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + */ + kind: typeof VSphereCluster; + metadata: metav1.IObjectMeta; + /** + * VSphereClusterSpec defines the desired state of VSphereCluster. + */ + spec?: { + /** + * ClusterModules hosts information regarding the anti-affinity vSphere constructs + * for each of the objects responsible for creation of VM objects belonging to the cluster. + */ + clusterModules?: { + /** + * ControlPlane indicates whether the referred object is responsible for control plane nodes. + * Currently, only the KubeadmControlPlane objects have this flag set to true. + * Only a single object in the slice can have this value set to true. + */ + controlPlane: boolean; + /** + * ModuleUUID is the unique identifier of the `ClusterModule` used by the object. + */ + moduleUUID: string; + /** + * TargetObjectName points to the object that uses the Cluster Module information to enforce + * anti-affinity amongst its descendant VM objects. + */ + targetObjectName: string; + }[]; + /** + * ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + */ + controlPlaneEndpoint?: { + /** + * The hostname on which the API server is serving. + */ + host: string; + /** + * The port on which the API server is serving. + */ + port: number; + }; + /** + * FailureDomainSelector is the label selector to use for failure domain selection + * for the control plane nodes of the cluster. + * If not set (`nil`), selecting failure domains will be disabled. + * An empty value (`{}`) selects all existing failure domains. + * A valid selector will select all failure domains which match the selector. + */ + failureDomainSelector?: { + /** + * matchExpressions is a list of label selector requirements. The requirements are ANDed. + */ + matchExpressions?: { + /** + * key is the label key that the selector applies to. + */ + key: string; + /** + * operator represents a key's relationship to a set of values. + * Valid operators are In, NotIn, Exists and DoesNotExist. + */ + operator: string; + /** + * values is an array of string values. If the operator is In or NotIn, + * the values array must be non-empty. If the operator is Exists or DoesNotExist, + * the values array must be empty. This array is replaced during a strategic + * merge patch. + */ + values?: string[]; + }[]; + /** + * matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + * map is equivalent to an element of matchExpressions, whose key field is "key", the + * operator is "In", and the values array contains only "value". The requirements are ANDed. + */ + matchLabels?: { + [k: string]: string; + }; + }; + /** + * IdentityRef is a reference to either a Secret or VSphereClusterIdentity that contains + * the identity to use when reconciling the cluster. + */ + identityRef?: { + /** + * Kind of the identity. Can either be VSphereClusterIdentity or Secret + */ + kind: 'VSphereClusterIdentity' | 'Secret'; + /** + * Name of the identity. + */ + name: string; + }; + /** + * Server is the address of the vSphere endpoint. + */ + server?: string; + /** + * Thumbprint is the colon-separated SHA-1 checksum of the given vCenter server's host certificate + */ + thumbprint?: string; + }; + /** + * VSphereClusterStatus defines the observed state of VSphereClusterSpec. + */ + status?: { + /** + * Conditions defines current service state of the VSphereCluster. + */ + conditions?: { + /** + * Last time the condition transitioned from one status to another. + * This should be when the underlying condition changed. If that is not known, then using the time when + * the API field changed is acceptable. + */ + lastTransitionTime: string; + /** + * A human readable message indicating details about the transition. + * This field may be empty. + */ + message?: string; + /** + * The reason for the condition's last transition in CamelCase. + * The specific API may choose whether or not this field is considered a guaranteed API. + * This field may not be empty. + */ + reason?: string; + /** + * Severity provides an explicit classification of Reason code, so the users or machines can immediately + * understand the current situation and act accordingly. + * The Severity field MUST be set only when Status=False. + */ + severity?: string; + /** + * Status of the condition, one of True, False, Unknown. + */ + status: string; + /** + * Type of condition in CamelCase or in foo.example.com/CamelCase. + * Many .condition.type values are consistent across resources like Available, but because arbitrary conditions + * can be useful (see .node.status.conditions), the ability to deconflict is important. + */ + type: string; + }[]; + /** + * FailureDomains is a list of failure domain objects synced from the infrastructure provider. + */ + failureDomains?: { + /** + * FailureDomainSpec is the Schema for Cluster API failure domains. + * It allows controllers to understand how many failure domains a cluster can optionally span across. + */ + [k: string]: { + /** + * Attributes is a free form map of attributes an infrastructure provider might use or require. + */ + attributes?: { + [k: string]: string; + }; + /** + * ControlPlane determines if this failure domain is suitable for use by control plane machines. + */ + controlPlane?: boolean; + }; + }; + ready?: boolean; + /** + * VCenterVersion defines the version of the vCenter server defined in the spec. + */ + vCenterVersion?: string; + }; +} + +export const VSphereClusterList = 'VSphereClusterList'; + +export interface IVSphereClusterList extends metav1.IList { + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1'; + kind: typeof VSphereClusterList; +} + +export const VSphereMachine = 'VSphereMachine'; + +/** + * VSphereMachine is the Schema for the vspheremachines API. + */ +export interface IVSphereMachine { + /** + * APIVersion defines the versioned schema of this representation of an object. + * Servers should convert recognized schemas to the latest internal value, and + * may reject unrecognized values. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + */ + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1'; + /** + * Kind is a string value representing the REST resource this object represents. + * Servers may infer this from the endpoint the client submits requests to. + * Cannot be updated. + * In CamelCase. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + */ + kind: typeof VSphereMachine; + metadata: metav1.IObjectMeta; + /** + * VSphereMachineSpec defines the desired state of VSphereMachine. + */ + spec?: { + /** + * AdditionalDisksGiB holds the sizes of additional disks of the virtual machine, in GiB + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + additionalDisksGiB?: number[]; + /** + * CloneMode specifies the type of clone operation. + * The LinkedClone mode is only support for templates that have at least + * one snapshot. If the template has no snapshots, then CloneMode defaults + * to FullClone. + * When LinkedClone mode is enabled the DiskGiB field is ignored as it is + * not possible to expand disks of linked clones. + * Defaults to LinkedClone, but fails gracefully to FullClone if the source + * of the clone operation has no snapshots. + */ + cloneMode?: string; + /** + * CustomVMXKeys is a dictionary of advanced VMX options that can be set on VM + * Defaults to empty map + */ + customVMXKeys?: { + [k: string]: string; + }; + /** + * Datacenter is the name or inventory path of the datacenter in which the + * virtual machine is created/located. + * Defaults to * which selects the default datacenter. + */ + datacenter?: string; + /** + * Datastore is the name or inventory path of the datastore in which the + * virtual machine is created/located. + */ + datastore?: string; + /** + * DiskGiB is the size of a virtual machine's disk, in GiB. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + diskGiB?: number; + /** + * FailureDomain is the failure domain unique identifier this Machine should be attached to, as defined in Cluster API. + * For this infrastructure provider, the name is equivalent to the name of the VSphereDeploymentZone. + */ + failureDomain?: string; + /** + * Folder is the name or inventory path of the folder in which the + * virtual machine is created/located. + */ + folder?: string; + /** + * GuestSoftPowerOffTimeout sets the wait timeout for shutdown in the VM guest. + * The VM will be powered off forcibly after the timeout if the VM is still + * up and running when the PowerOffMode is set to trySoft. + * + * + * This parameter only applies when the PowerOffMode is set to trySoft. + * + * + * If omitted, the timeout defaults to 5 minutes. + */ + guestSoftPowerOffTimeout?: string; + /** + * HardwareVersion is the hardware version of the virtual machine. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Check the compatibility with the ESXi version before setting the value. + */ + hardwareVersion?: string; + /** + * MemoryMiB is the size of a virtual machine's memory, in MiB. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + memoryMiB?: number; + /** + * Network is the network configuration for this machine's VM. + */ + network: { + /** + * Devices is the list of network devices used by the virtual machine. + * TODO(akutz) Make sure at least one network matches the + * ClusterSpec.CloudProviderConfiguration.Network.Name + */ + devices: { + /** + * AddressesFromPools is a list of IPAddressPools that should be assigned + * to IPAddressClaims. The machine's cloud-init metadata will be populated + * with IPAddresses fulfilled by an IPAM provider. + */ + addressesFromPools?: { + /** + * APIGroup is the group for the resource being referenced. + * If APIGroup is not specified, the specified Kind must be in the core API group. + * For any other third-party types, APIGroup is required. + */ + apiGroup?: string; + /** + * Kind is the type of resource being referenced + */ + kind: string; + /** + * Name is the name of resource being referenced + */ + name: string; + }[]; + /** + * DeviceName may be used to explicitly assign a name to the network device + * as it exists in the guest operating system. + */ + deviceName?: string; + /** + * DHCP4 is a flag that indicates whether or not to use DHCP for IPv4 + * on this device. + * If true then IPAddrs should not contain any IPv4 addresses. + */ + dhcp4?: boolean; + /** + * DHCP4Overrides allows for the control over several DHCP behaviors. + * Overrides will only be applied when the corresponding DHCP flag is set. + * Only configured values will be sent, omitted values will default to + * distribution defaults. + * Dependent on support in the network stack for your distribution. + * For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides) + */ + dhcp4Overrides?: { + /** + * Hostname is the name which will be sent to the DHCP server instead of + * the machine's hostname. + */ + hostname?: string; + /** + * RouteMetric is used to prioritize routes for devices. A lower metric for + * an interface will have a higher priority. + */ + routeMetric?: number; + /** + * SendHostname when `true`, the hostname of the machine will be sent to the + * DHCP server. + */ + sendHostname?: boolean; + /** + * UseDNS when `true`, the DNS servers in the DHCP server will be used and + * take precedence. + */ + useDNS?: boolean; + /** + * UseDomains can take the values `true`, `false`, or `route`. When `true`, + * the domain name from the DHCP server will be used as the DNS search + * domain for this device. When `route`, the domain name from the DHCP + * response will be used for routing DNS only, not for searching. + */ + useDomains?: string; + /** + * UseHostname when `true`, the hostname from the DHCP server will be set + * as the transient hostname of the machine. + */ + useHostname?: boolean; + /** + * UseMTU when `true`, the MTU from the DHCP server will be set as the + * MTU of the device. + */ + useMTU?: boolean; + /** + * UseNTP when `true`, the NTP servers from the DHCP server will be used + * by systemd-timesyncd and take precedence. + */ + useNTP?: boolean; + /** + * UseRoutes when `true`, the routes from the DHCP server will be installed + * in the routing table. + */ + useRoutes?: string; + }; + /** + * DHCP6 is a flag that indicates whether or not to use DHCP for IPv6 + * on this device. + * If true then IPAddrs should not contain any IPv6 addresses. + */ + dhcp6?: boolean; + /** + * DHCP6Overrides allows for the control over several DHCP behaviors. + * Overrides will only be applied when the corresponding DHCP flag is set. + * Only configured values will be sent, omitted values will default to + * distribution defaults. + * Dependent on support in the network stack for your distribution. + * For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides) + */ + dhcp6Overrides?: { + /** + * Hostname is the name which will be sent to the DHCP server instead of + * the machine's hostname. + */ + hostname?: string; + /** + * RouteMetric is used to prioritize routes for devices. A lower metric for + * an interface will have a higher priority. + */ + routeMetric?: number; + /** + * SendHostname when `true`, the hostname of the machine will be sent to the + * DHCP server. + */ + sendHostname?: boolean; + /** + * UseDNS when `true`, the DNS servers in the DHCP server will be used and + * take precedence. + */ + useDNS?: boolean; + /** + * UseDomains can take the values `true`, `false`, or `route`. When `true`, + * the domain name from the DHCP server will be used as the DNS search + * domain for this device. When `route`, the domain name from the DHCP + * response will be used for routing DNS only, not for searching. + */ + useDomains?: string; + /** + * UseHostname when `true`, the hostname from the DHCP server will be set + * as the transient hostname of the machine. + */ + useHostname?: boolean; + /** + * UseMTU when `true`, the MTU from the DHCP server will be set as the + * MTU of the device. + */ + useMTU?: boolean; + /** + * UseNTP when `true`, the NTP servers from the DHCP server will be used + * by systemd-timesyncd and take precedence. + */ + useNTP?: boolean; + /** + * UseRoutes when `true`, the routes from the DHCP server will be installed + * in the routing table. + */ + useRoutes?: string; + }; + /** + * Gateway4 is the IPv4 gateway used by this device. + * Required when DHCP4 is false. + */ + gateway4?: string; + /** + * Gateway4 is the IPv4 gateway used by this device. + */ + gateway6?: string; + /** + * IPAddrs is a list of one or more IPv4 and/or IPv6 addresses to assign + * to this device. IP addresses must also specify the segment length in + * CIDR notation. + * Required when DHCP4, DHCP6 and SkipIPAllocation are false. + */ + ipAddrs?: string[]; + /** + * MACAddr is the MAC address used by this device. + * It is generally a good idea to omit this field and allow a MAC address + * to be generated. + * Please note that this value must use the VMware OUI to work with the + * in-tree vSphere cloud provider. + */ + macAddr?: string; + /** + * MTU is the device’s Maximum Transmission Unit size in bytes. + */ + mtu?: number; + /** + * Nameservers is a list of IPv4 and/or IPv6 addresses used as DNS + * nameservers. + * Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). + */ + nameservers?: string[]; + /** + * NetworkName is the name of the vSphere network to which the device + * will be connected. + */ + networkName: string; + /** + * Routes is a list of optional, static routes applied to the device. + */ + routes?: { + /** + * Metric is the weight/priority of the route. + */ + metric: number; + /** + * To is an IPv4 or IPv6 address. + */ + to: string; + /** + * Via is an IPv4 or IPv6 address. + */ + via: string; + }[]; + /** + * SearchDomains is a list of search domains used when resolving IP + * addresses with DNS. + */ + searchDomains?: string[]; + /** + * SkipIPAllocation allows the device to not have IP address or DHCP configured. + * This is suitable for devices for which IP allocation is handled externally, eg. using Multus CNI. + * If true, CAPV will not verify IP address allocation. + */ + skipIPAllocation?: boolean; + }[]; + /** + * PreferredAPIServeCIDR is the preferred CIDR for the Kubernetes API + * server endpoint on this machine + * + * + * Deprecated: This field is going to be removed in a future release. + */ + preferredAPIServerCidr?: string; + /** + * Routes is a list of optional, static routes applied to the virtual + * machine. + */ + routes?: { + /** + * Metric is the weight/priority of the route. + */ + metric: number; + /** + * To is an IPv4 or IPv6 address. + */ + to: string; + /** + * Via is an IPv4 or IPv6 address. + */ + via: string; + }[]; + }; + /** + * NumCPUs is the number of virtual processors in a virtual machine. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + numCPUs?: number; + /** + * NumCPUs is the number of cores among which to distribute CPUs in this + * virtual machine. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + numCoresPerSocket?: number; + /** + * OS is the Operating System of the virtual machine + * Defaults to Linux + */ + os?: string; + /** + * PciDevices is the list of pci devices used by the virtual machine. + */ + pciDevices?: { + /** + * CustomLabel is the hardware label of a virtual machine's PCI device. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + customLabel?: string; + /** + * DeviceID is the device ID of a virtual machine's PCI, in integer. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Mutually exclusive with VGPUProfile as VGPUProfile and DeviceID + VendorID + * are two independent ways to define PCI devices. + */ + deviceId?: number; + /** + * VGPUProfile is the profile name of a virtual machine's vGPU, in string. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Mutually exclusive with DeviceID and VendorID as VGPUProfile and DeviceID + VendorID + * are two independent ways to define PCI devices. + */ + vGPUProfile?: string; + /** + * VendorId is the vendor ID of a virtual machine's PCI, in integer. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Mutually exclusive with VGPUProfile as VGPUProfile and DeviceID + VendorID + * are two independent ways to define PCI devices. + */ + vendorId?: number; + }[]; + /** + * PowerOffMode describes the desired behavior when powering off a VM. + * + * + * There are three, supported power off modes: hard, soft, and + * trySoft. The first mode, hard, is the equivalent of a physical + * system's power cord being ripped from the wall. The soft mode + * requires the VM's guest to have VM Tools installed and attempts to + * gracefully shut down the VM. Its variant, trySoft, first attempts + * a graceful shutdown, and if that fails or the VM is not in a powered off + * state after reaching the GuestSoftPowerOffTimeout, the VM is halted. + * + * + * If omitted, the mode defaults to hard. + */ + powerOffMode?: 'hard' | 'soft' | 'trySoft'; + /** + * ProviderID is the virtual machine's BIOS UUID formated as + * vsphere://12345678-1234-1234-1234-123456789abc + */ + providerID?: string; + /** + * ResourcePool is the name or inventory path of the resource pool in which + * the virtual machine is created/located. + */ + resourcePool?: string; + /** + * Server is the IP address or FQDN of the vSphere server on which + * the virtual machine is created/located. + */ + server?: string; + /** + * Snapshot is the name of the snapshot from which to create a linked clone. + * This field is ignored if LinkedClone is not enabled. + * Defaults to the source's current snapshot. + */ + snapshot?: string; + /** + * StoragePolicyName of the storage policy to use with this + * Virtual Machine + */ + storagePolicyName?: string; + /** + * TagIDs is an optional set of tags to add to an instance. Specified tagIDs + * must use URN-notation instead of display names. + */ + tagIDs?: string[]; + /** + * Template is the name or inventory path of the template used to clone + * the virtual machine. + */ + template: string; + /** + * Thumbprint is the colon-separated SHA-1 checksum of the given vCenter server's host certificate + * When this is set to empty, this VirtualMachine would be created + * without TLS certificate validation of the communication between Cluster API Provider vSphere + * and the VMware vCenter server. + */ + thumbprint?: string; + }; + /** + * VSphereMachineStatus defines the observed state of VSphereMachine. + */ + status?: { + /** + * Addresses contains the VSphere instance associated addresses. + */ + addresses?: { + /** + * The machine address. + */ + address: string; + /** + * Machine address type, one of Hostname, ExternalIP, InternalIP, ExternalDNS or InternalDNS. + */ + type: string; + }[]; + /** + * Conditions defines current service state of the VSphereMachine. + */ + conditions?: { + /** + * Last time the condition transitioned from one status to another. + * This should be when the underlying condition changed. If that is not known, then using the time when + * the API field changed is acceptable. + */ + lastTransitionTime: string; + /** + * A human readable message indicating details about the transition. + * This field may be empty. + */ + message?: string; + /** + * The reason for the condition's last transition in CamelCase. + * The specific API may choose whether or not this field is considered a guaranteed API. + * This field may not be empty. + */ + reason?: string; + /** + * Severity provides an explicit classification of Reason code, so the users or machines can immediately + * understand the current situation and act accordingly. + * The Severity field MUST be set only when Status=False. + */ + severity?: string; + /** + * Status of the condition, one of True, False, Unknown. + */ + status: string; + /** + * Type of condition in CamelCase or in foo.example.com/CamelCase. + * Many .condition.type values are consistent across resources like Available, but because arbitrary conditions + * can be useful (see .node.status.conditions), the ability to deconflict is important. + */ + type: string; + }[]; + /** + * FailureMessage will be set in the event that there is a terminal problem + * reconciling the Machine and will contain a more verbose string suitable + * for logging and human consumption. + * + * + * This field should not be set for transitive errors that a controller + * faces that are expected to be fixed automatically over + * time (like service outages), but instead indicate that something is + * fundamentally wrong with the Machine's spec or the configuration of + * the controller, and that manual intervention is required. Examples + * of terminal errors would be invalid combinations of settings in the + * spec, values that are unsupported by the controller, or the + * responsible controller itself being critically misconfigured. + * + * + * Any transient errors that occur during the reconciliation of Machines + * can be added as events to the Machine object and/or logged in the + * controller's output. + */ + failureMessage?: string; + /** + * FailureReason will be set in the event that there is a terminal problem + * reconciling the Machine and will contain a succinct value suitable + * for machine interpretation. + * + * + * This field should not be set for transitive errors that a controller + * faces that are expected to be fixed automatically over + * time (like service outages), but instead indicate that something is + * fundamentally wrong with the Machine's spec or the configuration of + * the controller, and that manual intervention is required. Examples + * of terminal errors would be invalid combinations of settings in the + * spec, values that are unsupported by the controller, or the + * responsible controller itself being critically misconfigured. + * + * + * Any transient errors that occur during the reconciliation of Machines + * can be added as events to the Machine object and/or logged in the + * controller's output. + */ + failureReason?: string; + /** + * Network returns the network status for each of the machine's configured + * network interfaces. + */ + network?: { + /** + * Connected is a flag that indicates whether this network is currently + * connected to the VM. + */ + connected?: boolean; + /** + * IPAddrs is one or more IP addresses reported by vm-tools. + */ + ipAddrs?: string[]; + /** + * MACAddr is the MAC address of the network device. + */ + macAddr: string; + /** + * NetworkName is the name of the network. + */ + networkName?: string; + }[]; + /** + * Ready is true when the provider resource is ready. + */ + ready?: boolean; + }; +} + +export const VSphereMachineList = 'VSphereMachineList'; + +export interface IVSphereMachineList extends metav1.IList { + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1'; + kind: typeof VSphereMachineList; +} + +export const VSphereMachineTemplate = 'VSphereMachineTemplate'; + +/** + * VSphereMachineTemplate is the Schema for the vspheremachinetemplates API. + */ +export interface IVSphereMachineTemplate { + /** + * APIVersion defines the versioned schema of this representation of an object. + * Servers should convert recognized schemas to the latest internal value, and + * may reject unrecognized values. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + */ + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1'; + /** + * Kind is a string value representing the REST resource this object represents. + * Servers may infer this from the endpoint the client submits requests to. + * Cannot be updated. + * In CamelCase. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + */ + kind: typeof VSphereMachineTemplate; + metadata: metav1.IObjectMeta; + /** + * VSphereMachineTemplateSpec defines the desired state of VSphereMachineTemplate. + */ + spec?: { + /** + * VSphereMachineTemplateResource describes the data needed to create a VSphereMachine from a template. + */ + template: { + /** + * Standard object's metadata. + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + */ + metadata?: { + /** + * Annotations is an unstructured key value map stored with a resource that may be + * set by external tools to store and retrieve arbitrary metadata. They are not + * queryable and should be preserved when modifying objects. + * More info: http://kubernetes.io/docs/user-guide/annotations + */ + annotations?: { + [k: string]: string; + }; + /** + * Map of string keys and values that can be used to organize and categorize + * (scope and select) objects. May match selectors of replication controllers + * and services. + * More info: http://kubernetes.io/docs/user-guide/labels + */ + labels?: { + [k: string]: string; + }; + }; + /** + * Spec is the specification of the desired behavior of the machine. + */ + spec: { + /** + * AdditionalDisksGiB holds the sizes of additional disks of the virtual machine, in GiB + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + additionalDisksGiB?: number[]; + /** + * CloneMode specifies the type of clone operation. + * The LinkedClone mode is only support for templates that have at least + * one snapshot. If the template has no snapshots, then CloneMode defaults + * to FullClone. + * When LinkedClone mode is enabled the DiskGiB field is ignored as it is + * not possible to expand disks of linked clones. + * Defaults to LinkedClone, but fails gracefully to FullClone if the source + * of the clone operation has no snapshots. + */ + cloneMode?: string; + /** + * CustomVMXKeys is a dictionary of advanced VMX options that can be set on VM + * Defaults to empty map + */ + customVMXKeys?: { + [k: string]: string; + }; + /** + * Datacenter is the name or inventory path of the datacenter in which the + * virtual machine is created/located. + * Defaults to * which selects the default datacenter. + */ + datacenter?: string; + /** + * Datastore is the name or inventory path of the datastore in which the + * virtual machine is created/located. + */ + datastore?: string; + /** + * DiskGiB is the size of a virtual machine's disk, in GiB. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + diskGiB?: number; + /** + * FailureDomain is the failure domain unique identifier this Machine should be attached to, as defined in Cluster API. + * For this infrastructure provider, the name is equivalent to the name of the VSphereDeploymentZone. + */ + failureDomain?: string; + /** + * Folder is the name or inventory path of the folder in which the + * virtual machine is created/located. + */ + folder?: string; + /** + * GuestSoftPowerOffTimeout sets the wait timeout for shutdown in the VM guest. + * The VM will be powered off forcibly after the timeout if the VM is still + * up and running when the PowerOffMode is set to trySoft. + * + * + * This parameter only applies when the PowerOffMode is set to trySoft. + * + * + * If omitted, the timeout defaults to 5 minutes. + */ + guestSoftPowerOffTimeout?: string; + /** + * HardwareVersion is the hardware version of the virtual machine. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Check the compatibility with the ESXi version before setting the value. + */ + hardwareVersion?: string; + /** + * MemoryMiB is the size of a virtual machine's memory, in MiB. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + memoryMiB?: number; + /** + * Network is the network configuration for this machine's VM. + */ + network: { + /** + * Devices is the list of network devices used by the virtual machine. + * TODO(akutz) Make sure at least one network matches the + * ClusterSpec.CloudProviderConfiguration.Network.Name + */ + devices: { + /** + * AddressesFromPools is a list of IPAddressPools that should be assigned + * to IPAddressClaims. The machine's cloud-init metadata will be populated + * with IPAddresses fulfilled by an IPAM provider. + */ + addressesFromPools?: { + /** + * APIGroup is the group for the resource being referenced. + * If APIGroup is not specified, the specified Kind must be in the core API group. + * For any other third-party types, APIGroup is required. + */ + apiGroup?: string; + /** + * Kind is the type of resource being referenced + */ + kind: string; + /** + * Name is the name of resource being referenced + */ + name: string; + }[]; + /** + * DeviceName may be used to explicitly assign a name to the network device + * as it exists in the guest operating system. + */ + deviceName?: string; + /** + * DHCP4 is a flag that indicates whether or not to use DHCP for IPv4 + * on this device. + * If true then IPAddrs should not contain any IPv4 addresses. + */ + dhcp4?: boolean; + /** + * DHCP4Overrides allows for the control over several DHCP behaviors. + * Overrides will only be applied when the corresponding DHCP flag is set. + * Only configured values will be sent, omitted values will default to + * distribution defaults. + * Dependent on support in the network stack for your distribution. + * For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides) + */ + dhcp4Overrides?: { + /** + * Hostname is the name which will be sent to the DHCP server instead of + * the machine's hostname. + */ + hostname?: string; + /** + * RouteMetric is used to prioritize routes for devices. A lower metric for + * an interface will have a higher priority. + */ + routeMetric?: number; + /** + * SendHostname when `true`, the hostname of the machine will be sent to the + * DHCP server. + */ + sendHostname?: boolean; + /** + * UseDNS when `true`, the DNS servers in the DHCP server will be used and + * take precedence. + */ + useDNS?: boolean; + /** + * UseDomains can take the values `true`, `false`, or `route`. When `true`, + * the domain name from the DHCP server will be used as the DNS search + * domain for this device. When `route`, the domain name from the DHCP + * response will be used for routing DNS only, not for searching. + */ + useDomains?: string; + /** + * UseHostname when `true`, the hostname from the DHCP server will be set + * as the transient hostname of the machine. + */ + useHostname?: boolean; + /** + * UseMTU when `true`, the MTU from the DHCP server will be set as the + * MTU of the device. + */ + useMTU?: boolean; + /** + * UseNTP when `true`, the NTP servers from the DHCP server will be used + * by systemd-timesyncd and take precedence. + */ + useNTP?: boolean; + /** + * UseRoutes when `true`, the routes from the DHCP server will be installed + * in the routing table. + */ + useRoutes?: string; + }; + /** + * DHCP6 is a flag that indicates whether or not to use DHCP for IPv6 + * on this device. + * If true then IPAddrs should not contain any IPv6 addresses. + */ + dhcp6?: boolean; + /** + * DHCP6Overrides allows for the control over several DHCP behaviors. + * Overrides will only be applied when the corresponding DHCP flag is set. + * Only configured values will be sent, omitted values will default to + * distribution defaults. + * Dependent on support in the network stack for your distribution. + * For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides) + */ + dhcp6Overrides?: { + /** + * Hostname is the name which will be sent to the DHCP server instead of + * the machine's hostname. + */ + hostname?: string; + /** + * RouteMetric is used to prioritize routes for devices. A lower metric for + * an interface will have a higher priority. + */ + routeMetric?: number; + /** + * SendHostname when `true`, the hostname of the machine will be sent to the + * DHCP server. + */ + sendHostname?: boolean; + /** + * UseDNS when `true`, the DNS servers in the DHCP server will be used and + * take precedence. + */ + useDNS?: boolean; + /** + * UseDomains can take the values `true`, `false`, or `route`. When `true`, + * the domain name from the DHCP server will be used as the DNS search + * domain for this device. When `route`, the domain name from the DHCP + * response will be used for routing DNS only, not for searching. + */ + useDomains?: string; + /** + * UseHostname when `true`, the hostname from the DHCP server will be set + * as the transient hostname of the machine. + */ + useHostname?: boolean; + /** + * UseMTU when `true`, the MTU from the DHCP server will be set as the + * MTU of the device. + */ + useMTU?: boolean; + /** + * UseNTP when `true`, the NTP servers from the DHCP server will be used + * by systemd-timesyncd and take precedence. + */ + useNTP?: boolean; + /** + * UseRoutes when `true`, the routes from the DHCP server will be installed + * in the routing table. + */ + useRoutes?: string; + }; + /** + * Gateway4 is the IPv4 gateway used by this device. + * Required when DHCP4 is false. + */ + gateway4?: string; + /** + * Gateway4 is the IPv4 gateway used by this device. + */ + gateway6?: string; + /** + * IPAddrs is a list of one or more IPv4 and/or IPv6 addresses to assign + * to this device. IP addresses must also specify the segment length in + * CIDR notation. + * Required when DHCP4, DHCP6 and SkipIPAllocation are false. + */ + ipAddrs?: string[]; + /** + * MACAddr is the MAC address used by this device. + * It is generally a good idea to omit this field and allow a MAC address + * to be generated. + * Please note that this value must use the VMware OUI to work with the + * in-tree vSphere cloud provider. + */ + macAddr?: string; + /** + * MTU is the device’s Maximum Transmission Unit size in bytes. + */ + mtu?: number; + /** + * Nameservers is a list of IPv4 and/or IPv6 addresses used as DNS + * nameservers. + * Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). + */ + nameservers?: string[]; + /** + * NetworkName is the name of the vSphere network to which the device + * will be connected. + */ + networkName: string; + /** + * Routes is a list of optional, static routes applied to the device. + */ + routes?: { + /** + * Metric is the weight/priority of the route. + */ + metric: number; + /** + * To is an IPv4 or IPv6 address. + */ + to: string; + /** + * Via is an IPv4 or IPv6 address. + */ + via: string; + }[]; + /** + * SearchDomains is a list of search domains used when resolving IP + * addresses with DNS. + */ + searchDomains?: string[]; + /** + * SkipIPAllocation allows the device to not have IP address or DHCP configured. + * This is suitable for devices for which IP allocation is handled externally, eg. using Multus CNI. + * If true, CAPV will not verify IP address allocation. + */ + skipIPAllocation?: boolean; + }[]; + /** + * PreferredAPIServeCIDR is the preferred CIDR for the Kubernetes API + * server endpoint on this machine + * + * + * Deprecated: This field is going to be removed in a future release. + */ + preferredAPIServerCidr?: string; + /** + * Routes is a list of optional, static routes applied to the virtual + * machine. + */ + routes?: { + /** + * Metric is the weight/priority of the route. + */ + metric: number; + /** + * To is an IPv4 or IPv6 address. + */ + to: string; + /** + * Via is an IPv4 or IPv6 address. + */ + via: string; + }[]; + }; + /** + * NumCPUs is the number of virtual processors in a virtual machine. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + numCPUs?: number; + /** + * NumCPUs is the number of cores among which to distribute CPUs in this + * virtual machine. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + numCoresPerSocket?: number; + /** + * OS is the Operating System of the virtual machine + * Defaults to Linux + */ + os?: string; + /** + * PciDevices is the list of pci devices used by the virtual machine. + */ + pciDevices?: { + /** + * CustomLabel is the hardware label of a virtual machine's PCI device. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + */ + customLabel?: string; + /** + * DeviceID is the device ID of a virtual machine's PCI, in integer. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Mutually exclusive with VGPUProfile as VGPUProfile and DeviceID + VendorID + * are two independent ways to define PCI devices. + */ + deviceId?: number; + /** + * VGPUProfile is the profile name of a virtual machine's vGPU, in string. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Mutually exclusive with DeviceID and VendorID as VGPUProfile and DeviceID + VendorID + * are two independent ways to define PCI devices. + */ + vGPUProfile?: string; + /** + * VendorId is the vendor ID of a virtual machine's PCI, in integer. + * Defaults to the eponymous property value in the template from which the + * virtual machine is cloned. + * Mutually exclusive with VGPUProfile as VGPUProfile and DeviceID + VendorID + * are two independent ways to define PCI devices. + */ + vendorId?: number; + }[]; + /** + * PowerOffMode describes the desired behavior when powering off a VM. + * + * + * There are three, supported power off modes: hard, soft, and + * trySoft. The first mode, hard, is the equivalent of a physical + * system's power cord being ripped from the wall. The soft mode + * requires the VM's guest to have VM Tools installed and attempts to + * gracefully shut down the VM. Its variant, trySoft, first attempts + * a graceful shutdown, and if that fails or the VM is not in a powered off + * state after reaching the GuestSoftPowerOffTimeout, the VM is halted. + * + * + * If omitted, the mode defaults to hard. + */ + powerOffMode?: 'hard' | 'soft' | 'trySoft'; + /** + * ProviderID is the virtual machine's BIOS UUID formated as + * vsphere://12345678-1234-1234-1234-123456789abc + */ + providerID?: string; + /** + * ResourcePool is the name or inventory path of the resource pool in which + * the virtual machine is created/located. + */ + resourcePool?: string; + /** + * Server is the IP address or FQDN of the vSphere server on which + * the virtual machine is created/located. + */ + server?: string; + /** + * Snapshot is the name of the snapshot from which to create a linked clone. + * This field is ignored if LinkedClone is not enabled. + * Defaults to the source's current snapshot. + */ + snapshot?: string; + /** + * StoragePolicyName of the storage policy to use with this + * Virtual Machine + */ + storagePolicyName?: string; + /** + * TagIDs is an optional set of tags to add to an instance. Specified tagIDs + * must use URN-notation instead of display names. + */ + tagIDs?: string[]; + /** + * Template is the name or inventory path of the template used to clone + * the virtual machine. + */ + template: string; + /** + * Thumbprint is the colon-separated SHA-1 checksum of the given vCenter server's host certificate + * When this is set to empty, this VirtualMachine would be created + * without TLS certificate validation of the communication between Cluster API Provider vSphere + * and the VMware vCenter server. + */ + thumbprint?: string; + }; + }; + }; +} + +export const VSphereMachineTemplateList = 'VSphereMachineTemplateList'; + +export interface IVSphereMachineTemplateList + extends metav1.IList { + apiVersion: 'infrastructure.cluster.x-k8s.io/v1beta1'; + kind: typeof VSphereMachineTemplateList; +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 8fa0f821b4..c9f46df191 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -150,6 +150,15 @@ export function convertMBtoBytes(mb: number): number { return mb * 1e6; } +/** + * Convert mebibytes to bytes. + * @param mebibytes + */ +export function convertMiBtoBytes(mebibytes: number): number { + // eslint-disable-next-line no-magic-numbers + return mebibytes * 1024 * 1024; +} + /** * Helper method that validates an object based on constraints. * @param validatable - The object to validate.