diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index 88bc2d9f6..cef96dff4 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -1394,6 +1394,7 @@ "View pod logs": "View pod logs", "View virtualization dashboard": "View virtualization dashboard", "View YAML & CLI": "View YAML & CLI", + "Virtctl is disabled for this namespace as it's managed by": "Virtctl is disabled for this namespace as it's managed by", "virtio": "virtio", "VirtIO": "VirtIO", "Virtual machine is not running": "Virtual machine is not running", diff --git a/package.json b/package.json index b577d6f7f..f62644404 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ }, "devDependencies": { "@cypress/webpack-preprocessor": "^6.0.2", - "@kubevirt-ui/kubevirt-api": "^1.3.3", + "@kubevirt-ui/kubevirt-api": "^1.3.5", "@openshift-console/dynamic-plugin-sdk": "1.8.0", "@openshift-console/dynamic-plugin-sdk-internal": "1.0.0", "@openshift-console/dynamic-plugin-sdk-webpack": "^1.3.0", diff --git a/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx b/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx index 3146cbdcc..b3855c64b 100644 --- a/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx +++ b/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx @@ -5,6 +5,14 @@ import { Link } from 'react-router-dom-v5-compat'; import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { getConsoleVirtctlCommand } from '@kubevirt-utils/components/SSHAccess/utils'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { + ClusterUserDefinedNetworkModelGroupVersionKind, + UserDefinedNetworkModelGroupVersionKind, +} from '@kubevirt-utils/models'; +import { getName, getNamespace } from '@kubevirt-utils/resources/shared'; +import useNamespaceUDN from '@kubevirt-utils/resources/udn/hooks/useNamespaceUDN'; +import { isEmpty } from '@kubevirt-utils/utils/utils'; +import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; import { DescriptionListDescription, DescriptionListGroup, @@ -26,6 +34,10 @@ type ConsoleOverVirtctlProps = { const ConsoleOverVirtctl: FC = ({ vm }) => { const { t } = useKubevirtTranslation(); + const [udn, clusterUDN] = useNamespaceUDN(getNamespace(vm)); + + const namespaceManagedByUDN = !isEmpty(udn) || !isEmpty(clusterUDN); + return ( @@ -68,7 +80,23 @@ const ConsoleOverVirtctl: FC = ({ vm }) => { - + {namespaceManagedByUDN ? ( + <> + {t("Virtctl is disabled for this namespace as it's managed by")}{' '} + {' '} + + ) : ( + + )} ); diff --git a/src/utils/resources/udn/hooks/useNamespaceUDN.ts b/src/utils/resources/udn/hooks/useNamespaceUDN.ts new file mode 100644 index 000000000..691e0492e --- /dev/null +++ b/src/utils/resources/udn/hooks/useNamespaceUDN.ts @@ -0,0 +1,43 @@ +import { + ClusterUserDefinedNetworkModelGroupVersionKind, + modelToGroupVersionKind, + ProjectModel, + UserDefinedNetworkModelGroupVersionKind, +} from '@kubevirt-utils/models'; +import { + ClusterUserDefinedNetworkKind, + UserDefinedNetworkKind, + UserDefinedNetworkRole, +} from '@kubevirt-utils/resources/udn/types'; +import { matchSelector } from '@kubevirt-utils/utils/matchSelector'; +import { K8sResourceCommon, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; + +const useNamespaceUDN = (namespace: string) => { + const [project] = useK8sWatchResource({ + groupVersionKind: modelToGroupVersionKind(ProjectModel), + name: namespace, + }); + + const [udns] = useK8sWatchResource({ + groupVersionKind: UserDefinedNetworkModelGroupVersionKind, + isList: true, + namespace, + }); + + const [clusterUDNs] = useK8sWatchResource({ + groupVersionKind: ClusterUserDefinedNetworkModelGroupVersionKind, + isList: true, + }); + + const primaryUDN = udns?.find( + (udn) => udn?.spec?.layer2?.role === UserDefinedNetworkRole.Primary, + ); + + const primaryClustersUDNs = clusterUDNs + ?.filter((clusterUDN) => matchSelector(project, clusterUDN?.spec?.namespaceSelector)) + ?.find((udn) => udn?.spec?.network?.layer2?.role === UserDefinedNetworkRole.Primary); + + return [primaryUDN, primaryClustersUDNs]; +}; + +export default useNamespaceUDN; diff --git a/src/utils/resources/udn/types.ts b/src/utils/resources/udn/types.ts new file mode 100644 index 000000000..9caa3a705 --- /dev/null +++ b/src/utils/resources/udn/types.ts @@ -0,0 +1,51 @@ +import { K8sResourceKind, Selector } from '@openshift-console/dynamic-plugin-sdk'; + +export type UserDefinedNetworkAnnotations = { + description?: string; +}; + +export enum UserDefinedNetworkRole { + Primary = 'Primary', + Secondary = 'Secondary', +} + +export type UserDefinedNetworkLayer2 = { + ipamLifecycle?: string; + joinSubnets?: string[]; + mtu?: number; + role: UserDefinedNetworkRole; + subnets?: string[]; +}; + +export type UserDefinedNetworkLayer3Subnet = { + cidr: string; + hostSubnet?: number; +}; + +export type UserDefinedNetworkSubnet = string | UserDefinedNetworkLayer3Subnet; + +export type UserDefinedNetworkLayer3 = { + joinSubnets?: string[]; + mtu?: number; + role: string; + subnets?: UserDefinedNetworkLayer3Subnet[]; +}; + +export type ClusterUserDefinedNetworkSpec = { + namespaceSelector?: Selector; + network?: UserDefinedNetworkSpec; +}; + +export type UserDefinedNetworkSpec = { + layer2?: UserDefinedNetworkLayer2; + layer3?: UserDefinedNetworkLayer3; + topology: string; +}; + +export type ClusterUserDefinedNetworkKind = { + spec?: ClusterUserDefinedNetworkSpec; +} & K8sResourceKind; + +export type UserDefinedNetworkKind = { + spec?: UserDefinedNetworkSpec; +} & K8sResourceKind; diff --git a/src/utils/utils/matchSelector.ts b/src/utils/utils/matchSelector.ts new file mode 100644 index 000000000..de0f0391f --- /dev/null +++ b/src/utils/utils/matchSelector.ts @@ -0,0 +1,54 @@ +import { + K8sResourceCommon, + MatchExpression, + Operator, + Selector, +} from '@openshift-console/dynamic-plugin-sdk'; + +import { isEmpty } from './utils'; + +export const createEquals = (key: string, value: string): MatchExpression => ({ + key, + operator: Operator.Equals, + values: [value], +}); + +export const matchExpressionSatisfied = ( + expression: MatchExpression, + labels: { [key in string]: string }, +): boolean => { + switch (expression.operator) { + case Operator.Equals: + return labels?.[expression.key] === expression.values?.[0]; + case Operator.NotEqual: + return labels?.[expression.key] !== expression.values?.[0]; + case Operator.NotEquals: + return labels?.[expression.key] !== expression.values?.[0]; + case Operator.Exists: + return !isEmpty(labels?.[expression.key]); + case Operator.DoesNotExist: + return isEmpty(labels?.[expression.key]); + case Operator.GreaterThan: + return labels?.[expression.key] > expression.values?.[0]; + case Operator.LessThan: + return labels?.[expression.key] < expression.values?.[0]; + case Operator.In: + return expression.values?.includes(labels?.[expression.key]); + case Operator.NotIn: + return !expression.values?.includes(labels?.[expression.key]); + } +}; + +export const matchSelector = (resource: K8sResourceCommon, selector: Selector) => { + const { matchExpressions, matchLabels } = selector || {}; + + const requirements = Object.keys(matchLabels || {}) + .sort() + .map((matchLabel) => createEquals(matchLabel, matchLabels[matchLabel])); + + const allExpressions = [...requirements, ...(matchExpressions || [])]; + + return allExpressions.every((expression) => + matchExpressionSatisfied(expression, resource?.metadata?.labels), + ); +}; diff --git a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx index ae056d535..7a57df9d9 100644 --- a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx +++ b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx @@ -4,6 +4,9 @@ import { Link } from 'react-router-dom-v5-compat'; import { V1VirtualMachine, V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { VirtualMachineDetailsTab } from '@kubevirt-utils/constants/tabs-constants'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { getNamespace } from '@kubevirt-utils/resources/shared'; +import useNamespaceUDN from '@kubevirt-utils/resources/udn/hooks/useNamespaceUDN'; +import { isEmpty } from '@kubevirt-utils/utils/utils'; import { VirtualizedTable } from '@openshift-console/dynamic-plugin-sdk'; import { Card, CardBody, CardTitle, Divider } from '@patternfly/react-core'; @@ -29,6 +32,9 @@ const VirtualMachinesOverviewTabInterfaces: FC @@ -57,7 +63,7 @@ const VirtualMachinesOverviewTabInterfaces: FC - + {isNotUDNManaged && } diff --git a/yarn.lock b/yarn.lock index f99cb41d7..cdb268541 100644 --- a/yarn.lock +++ b/yarn.lock @@ -871,10 +871,10 @@ resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.5.0.tgz#6008e35b9d9d8ee27bc4bfaa70c8cbf33a537b4c" integrity sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA== -"@kubevirt-ui/kubevirt-api@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-1.3.3.tgz#c43ac3c392365c5a5651fd8b60f89c0f7ac5b4c2" - integrity sha512-YqZo/KKq9ndSq6YW052x9GzXMudQV8MSddgX4URO6UAKfEhVDrpKItspX97/dvBFv/qy0hMN7J5CXHDiTG3lMg== +"@kubevirt-ui/kubevirt-api@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-1.3.5.tgz#c9c10b30d8cc9770fbc6b08077b9a997fdc3ca58" + integrity sha512-FI9RXYwVl3/INr4z7uemuJk3D218DzxOSm4iiVjJIiYp5DHj+r5EqwTaaWi9NQQYj+aZUw3a1akd8p/JO8lMZw== "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4"