Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for adding OVA providers #546

Merged
merged 1 commit into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ci/yaml/ova-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: forklift.konveyor.io/v1beta1
kind: Provider
metadata:
name: ova-test
namespace: konveyor-forklift
spec:
secret: {}
type: ova
url: 'https://fake.coml:5000/files/ova'
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
"Please enter URL for OpenStack services REST APIs.": "Please enter URL for OpenStack services REST APIs.",
"Please enter URL for the kubernetes API server, if empty URL default to this cluster.": "Please enter URL for the kubernetes API server, if empty URL default to this cluster.",
"Please enter URL for vSphere REST APIs server.": "Please enter URL for vSphere REST APIs server.",
"Please enter URL.": "Please enter URL.",
"Pod network": "Pod network",
"Product": "Product",
"Project": "Project",
Expand Down Expand Up @@ -231,6 +232,7 @@
"Specify the API end point URL, for example, https://<identity_service>/v3 for OpenStack.": "Specify the API end point URL, for example, https://<identity_service>/v3 for OpenStack.",
"Specify the API end point URL, for example, https://<kubernetes API Endpoint>:6443 for OpenShift.": "Specify the API end point URL, for example, https://<kubernetes API Endpoint>:6443 for OpenShift.",
"Specify the API end point URL, for example, https://<vCenter_host>/sdk for vSphere.": "Specify the API end point URL, for example, https://<vCenter_host>/sdk for vSphere.",
"Specify the end point to the NFS server path serving the OVA file[s].": "Specify the end point to the NFS server path serving the OVA file[s].",
"Specify the VDDK image that you created. some functionality will not be available if the VDDK image is left empty": "Specify the VDDK image that you created. some functionality will not be available if the VDDK image is left empty",
"SSHA-1 fingerprint": "SSHA-1 fingerprint",
"Staging": "Staging",
Expand Down Expand Up @@ -270,6 +272,7 @@
"Updated": "Updated",
"URL": "URL",
"URL must start with https:// or http:// and contain valid hostname and path": "URL must start with https:// or http:// and contain valid hostname and path",
"URL of the OVA provider": "URL of the OVA provider",
"URL of the provider": "URL of the provider",
"URL of the provider, leave empty to use this providers URL": "URL of the provider, leave empty to use this providers URL",
"User Domain Name": "User Domain Name",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { EditModalProps } from '../EditModal';

import { OpenshiftEditURLModal } from './OpenshiftEditURLModal';
import { OpenstackEditURLModal } from './OpenstackEditURLModal';
import { OVAEditURLModal } from './OVAEditURLModal';
import { OvirtEditURLModal } from './OvirtEditURLModal';
import { VSphereEditURLModal } from './VSphereEditURLModal';

Expand All @@ -31,6 +32,8 @@ export const EditProviderURLModal: React.FC<EditProviderURLModalProps> = (props)
return <OpenstackEditURLModal {...props} />;
case 'vsphere':
return <VSphereEditURLModal {...props} />;
case 'ova':
return <OVAEditURLModal {...props} />;
default:
return <></>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { useForkliftTranslation } from 'src/utils/i18n';

import { ProviderModel } from '@kubev2v/types';
import { ModalVariant } from '@patternfly/react-core';

import { EditModal, ValidationHookType } from '../EditModal';

import { patchProviderURL } from './utils/patchProviderURL';
import { EditProviderURLModalProps } from './EditProviderURLModal';

export const OVAEditURLModal: React.FC<EditProviderURLModalProps> = (props) => {
const { t } = useForkliftTranslation();

const urlValidationHook: ValidationHookType = (value) => {

Check warning on line 15 in packages/forklift-console-plugin/src/modules/Providers/modals/EditProviderURL/OVAEditURLModal.tsx

View workflow job for this annotation

GitHub Actions / Run linter and tests

'value' is defined but never used
const isValidURL = true;

return isValidURL
? {
validationHelpText: undefined,
validated: 'success',
}
: {
validationHelpText: t(
'URL must start with https:// or http:// and contain valid hostname and path',
),
validated: 'error',
};
};

return (
<EditModal
{...props}
jsonPath={'spec.url'}
title={props?.title || t('Edit URL')}
label={props?.label || t('URL')}
model={ProviderModel}
variant={ModalVariant.large}
body={t('Specify the end point to the NFS server path serving the OVA file[s].')}
helperText={t('Please enter URL.')}
onConfirmHook={patchProviderURL}
validationHook={urlValidationHook}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export * from './EditProviderURLModal';
export * from './OpenshiftEditURLModal';
export * from './OpenstackEditURLModal';
export * from './OVAEditURLModal';
export * from './OvirtEditURLModal';
export * from './VSphereEditURLModal';
// @endindex
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface SelectableGalleryProps {
export const SelectableGallery: FC<SelectableGalleryProps> = ({
items,
onChange,
sortFunction = ([, a], [, b]) => a.title.localeCompare(b.title),
sortFunction,
selectedID,
}) => {
// State to manage the selected card's id
Expand All @@ -52,7 +52,9 @@ export const SelectableGallery: FC<SelectableGalleryProps> = ({
};

// Convert the items object to an array and sort it
const sortedItems = Object.entries(items).sort(sortFunction);
const sortedItems = sortFunction
? Object.entries(items).sort(sortFunction)
: Object.entries(items);

return (
<Gallery hasGutter className="forklift-selectable-gallery">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export function getIsOnlySource(provider: V1beta1Provider): boolean {
return SOURCE_ONLY_PROVIDER_TYPES.includes(provider?.spec.type as ProviderType);
}

export const SOURCE_ONLY_PROVIDER_TYPES: ProviderType[] = ['vsphere', 'ovirt', 'openstack'];
export const SOURCE_ONLY_PROVIDER_TYPES: ProviderType[] = ['ova', 'vsphere', 'ovirt', 'openstack'];
export const TARGET_PROVIDER_TYPES: ProviderType[] = ['openshift'];
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @index(['./*.tsx', './*.ts', /__/g], f => `export * from '${f.path}';`)
export * from './openshiftProviderValidator';
export * from './openstackProviderValidator';
export * from './ovaProviderValidator';
export * from './ovirtProviderValidator';
export * from './providerValidator';
export * from './vsphereProviderValidator';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { V1beta1Provider } from '@kubev2v/types';

import { validateK8sName } from '../common';

export function ovaProviderValidator(provider: V1beta1Provider) {
const name = provider?.metadata?.name;

if (!validateK8sName(name)) {
return new Error('invalided kubernetes resource name');
}

return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { V1beta1Provider } from '@kubev2v/types';

import { openshiftProviderValidator } from './openshiftProviderValidator';
import { openstackProviderValidator } from './openstackProviderValidator';
import { ovaProviderValidator } from './ovaProviderValidator';
import { ovirtProviderValidator } from './ovirtProviderValidator';
import { vsphereProviderValidator } from './vsphereProviderValidator';

Expand All @@ -21,6 +22,9 @@ export function providerValidator(provider: V1beta1Provider) {
case 'vsphere':
validationError = vsphereProviderValidator(provider);
break;
case 'ova':
validationError = ovaProviderValidator(provider);
break;
default:
validationError = new Error('bad provider type');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export function secretValidator(type: string, secret: V1Secret) {
case 'vsphere':
validationError = vsphereSecretValidator(secret);
break;
case 'ova':
validationError = null;
break;
default:
validationError = new Error('bad provider type');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {

import { OpenshiftProviderFormCreate } from './OpenshiftProviderCreateForm';
import { OpenstackProviderCreateForm } from './OpenstackProviderCreateForm';
import { OVAProviderCreateForm } from './OVAProviderCreateForm';
import { OvirtProviderCreateForm } from './OvirtProviderCreateForm';
import { ProvidersCreateFormProps } from './ProviderCreateForm';
import { VSphereProviderCreateForm } from './VSphereProviderCreateForm';
Expand Down Expand Up @@ -48,6 +49,12 @@ export const EditProvider: React.FC<ProvidersCreateFormProps> = ({
<VSphereCredentialsEdit secret={newSecret} onChange={onNewSecretChange} />
</>
);
case 'ova':
return (
<>
<OVAProviderCreateForm provider={newProvider} onChange={onNewProviderChange} />
</>
);
default:
return <></>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useCallback, useReducer } from 'react';
import { Validation } from 'src/modules/Providers/utils';
import { useForkliftTranslation } from 'src/utils/i18n';

import { V1beta1Provider } from '@kubev2v/types';
import { Form, FormGroup, TextInput } from '@patternfly/react-core';

export interface OVAProviderCreateFormProps {
provider: V1beta1Provider;
onChange: (newValue: V1beta1Provider) => void;
}

export const OVAProviderCreateForm: React.FC<OVAProviderCreateFormProps> = ({
provider,
onChange,
}) => {
const { t } = useForkliftTranslation();

const url = provider?.spec?.url || '';

const initialState = {
validation: {
url: 'default' as Validation,
},
};

const reducer = (state, action) => {
switch (action.type) {
case 'SET_FIELD_VALIDATED':
return {
...state,
validation: {
...state.validation,
[action.payload.field]: action.payload.validationState,
},
};
default:
return state;
}
};

const [state, dispatch] = useReducer(reducer, initialState);

const handleChange = useCallback(
(id, value) => {
const trimmedValue = value.trim();

if (id === 'url') {
const validationState = 'success';
dispatch({ type: 'SET_FIELD_VALIDATED', payload: { field: id, validationState } });

onChange({ ...provider, spec: { ...provider.spec, url: trimmedValue } });
}
},
[provider],
);

return (
<Form isWidthLimited className="forklift-section-provider-edit">
<FormGroup
label={t('URL')}
fieldId="url"
helperText={t('URL of the OVA provider')}
validated={state.validation.url}
helperTextInvalid={t('Error: URL must be valid.')}
>
<TextInput
type="text"
id="url"
name="url"
value={url}
validated={state.validation.url}
onChange={(value) => handleChange('url', value)}
/>
</FormGroup>
</Form>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './EditProvider';
export * from './OpenshiftProviderCreateForm';
export * from './OpenstackProviderCreateForm';
export * from './OVAProviderCreateForm';
export * from './OvirtProviderCreateForm';
export * from './providerCardItems';
export * from './ProviderCreateForm';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SelectableGalleryItem } from 'src/modules/Providers/utils/components/Ga

import openshiftImg from '../images/openshift.svg';
import openstackImg from '../images/openstack.svg';
import ovaImg from '../images/ova.svg';
import redhatImg from '../images/redhat.svg';
import vmImg from '../images/vm.svg';

Expand Down Expand Up @@ -42,6 +43,15 @@ const redhatLogo = (
/>
);

const ovaLogo = (
<img
className="forklift--create-provider-edit-card-title-logo"
src={ovaImg}
alt="PatternFly logo"
width="27px"
/>
);

export const providerCardItems: Record<string, SelectableGalleryItem> = {
openshift: {
title: 'OpenShift Virtualization',
Expand All @@ -63,4 +73,9 @@ export const providerCardItems: Record<string, SelectableGalleryItem> = {
logo: vmLogo,
content: "vSphere is VMware's cloud computing virtualization platform.",
},
ova: {
title: 'Open Virtual Appliance (OVA)',
logo: ovaLogo,
content: 'OVA file is a virtual appliance used by virtualization applications.',
},
};
Loading